@coreplane/switchboard 1.204.0 → 1.206.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 (389) hide show
  1. package/dist/assets/Dockerfile +6 -3
  2. package/dist/assets/config/config.example.yaml +56 -4
  3. package/dist/assets/deploy/cloudflare/coordinator.ts +86 -0
  4. package/dist/assets/deploy/cloudflare/shared.ts +9 -0
  5. package/dist/assets/deploy/cloudflare/worker.ts +142 -6
  6. package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +9 -0
  7. package/dist/assets/deploy/cloudflare-memory/worker.ts +381 -7
  8. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +22 -2
  9. package/dist/assets/deploy/cloudflare-resident/Dockerfile +89 -3
  10. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +81 -10
  11. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +4 -1
  12. package/dist/assets/deploy/secrets.manifest.json +1 -1
  13. package/dist/assets/package-lock.json +291 -97
  14. package/dist/assets/package.json +1 -1
  15. package/dist/assets/source.json +3 -3
  16. package/dist/assets/src/agents/registry.ts +517 -0
  17. package/dist/assets/src/config/profile.ts +229 -0
  18. package/dist/assets/src/core/authz/policy.ts +15 -0
  19. package/dist/assets/src/core/coordinator/contract.ts +239 -0
  20. package/dist/assets/src/core/coordinator/driver.ts +501 -0
  21. package/dist/assets/src/core/coordinator/instancesRoute.ts +181 -0
  22. package/dist/assets/src/core/delivery.ts +829 -0
  23. package/dist/assets/src/core/deliverySnapshotStore.ts +260 -0
  24. package/dist/assets/src/core/reviewVerdict.ts +296 -0
  25. package/dist/assets/src/core/reviewedHead.ts +77 -0
  26. package/dist/assets/src/core/runEvents.ts +4 -2
  27. package/dist/assets/src/core/runLedger/decisions.ts +2 -1
  28. package/dist/assets/src/core/runLedger/types.ts +23 -1
  29. package/dist/assets/src/core/runRecord.ts +100 -0
  30. package/dist/assets/src/core/ship/contract.ts +502 -0
  31. package/dist/assets/src/core/ship/coordinator.ts +1039 -0
  32. package/dist/assets/src/core/workerError.ts +12 -0
  33. package/dist/assets/web/dist/.vite/manifest.json +2770 -70
  34. package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +1 -0
  35. package/dist/assets/web/dist/assets/{CostsPage-XQ-fQzdz.js → CostsPage-DGSE48l1.js} +1 -1
  36. package/dist/assets/web/dist/assets/DeliveryPage-CvlWP7Eq.js +1 -0
  37. package/dist/assets/web/dist/assets/NotFoundPage-BzWS4Ca1.js +1 -0
  38. package/dist/assets/web/dist/assets/ResidentDetailPage-D3P21yeI.js +1 -0
  39. package/dist/assets/web/dist/assets/ResidentsIndexPage-DOYqnZ1q.js +1 -0
  40. package/dist/assets/web/dist/assets/RunRoutePage-OmvrvPXY.js +12 -0
  41. package/dist/assets/web/dist/assets/RunRoutePage-f6TSNPeW.css +1 -0
  42. package/dist/assets/web/dist/assets/RunsIndexPage-DWbSQtL4.js +1 -0
  43. package/dist/assets/web/dist/assets/RunsTabs-DEICQ4FY.js +1 -0
  44. package/dist/assets/web/dist/assets/ScheduledPage-CPKfJ4mR.js +1 -0
  45. package/dist/assets/web/dist/assets/StatusDot-COr8jTyM.js +1 -0
  46. package/dist/assets/web/dist/assets/Tooltip-fOqTZkNT.js +1 -0
  47. package/dist/assets/web/dist/assets/abap-CLvhMVsD.js +1 -0
  48. package/dist/assets/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  49. package/dist/assets/web/dist/assets/ada-C5qYipkI.js +1 -0
  50. package/dist/assets/web/dist/assets/ahk-CA9gdOdF.js +1 -0
  51. package/dist/assets/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  52. package/dist/assets/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  53. package/dist/assets/web/dist/assets/angular-html-Cw130Zyi.js +1 -0
  54. package/dist/assets/web/dist/assets/angular-ts-DY1m4C2s.js +1 -0
  55. package/dist/assets/web/dist/assets/apache-U0d_L8uA.js +1 -0
  56. package/dist/assets/web/dist/assets/apex-KFGFyKwG.js +1 -0
  57. package/dist/assets/web/dist/assets/apl-W6Ty7v05.js +1 -0
  58. package/dist/assets/web/dist/assets/applescript-CCn79oCD.js +1 -0
  59. package/dist/assets/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  60. package/dist/assets/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  61. package/dist/assets/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  62. package/dist/assets/web/dist/assets/astro-DlLVHwVk.js +1 -0
  63. package/dist/assets/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  64. package/dist/assets/web/dist/assets/awk-BWXHIvNe.js +1 -0
  65. package/dist/assets/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  66. package/dist/assets/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  67. package/dist/assets/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  68. package/dist/assets/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  69. package/dist/assets/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  70. package/dist/assets/web/dist/assets/beancount-D-usSTwE.js +1 -0
  71. package/dist/assets/web/dist/assets/berry-DKpUyyne.js +1 -0
  72. package/dist/assets/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  73. package/dist/assets/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  74. package/dist/assets/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  75. package/dist/assets/web/dist/assets/blade-Bco3tDh4.js +1 -0
  76. package/dist/assets/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  77. package/dist/assets/web/dist/assets/c-1LaFY_cj.js +1 -0
  78. package/dist/assets/web/dist/assets/c3-D0apG41V.js +1 -0
  79. package/dist/assets/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  80. package/dist/assets/web/dist/assets/cairo-DLTphjLi.js +1 -0
  81. package/dist/assets/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  82. package/dist/assets/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  83. package/dist/assets/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  84. package/dist/assets/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  85. package/dist/assets/web/dist/assets/chapel-B-OvrOL5.js +1 -0
  86. package/dist/assets/web/dist/assets/clarity-CtfBBspg.js +1 -0
  87. package/dist/assets/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  88. package/dist/assets/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  89. package/dist/assets/web/dist/assets/cobol-Ch3EPlHu.js +1 -0
  90. package/dist/assets/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  91. package/dist/assets/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  92. package/dist/assets/web/dist/assets/coffee-hZuITnto.js +1 -0
  93. package/dist/assets/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  94. package/dist/assets/web/dist/assets/coq-DuBRPxYg.js +1 -0
  95. package/dist/assets/web/dist/assets/cpp-B8iRVo8m.js +1 -0
  96. package/dist/assets/web/dist/assets/crystal-DKveI7lr.js +1 -0
  97. package/dist/assets/web/dist/assets/csharp-oqKa8noW.js +1 -0
  98. package/dist/assets/web/dist/assets/css-_2_CD-rr.js +1 -0
  99. package/dist/assets/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  100. package/dist/assets/web/dist/assets/cue-CE9AQfxI.js +1 -0
  101. package/dist/assets/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  102. package/dist/assets/web/dist/assets/d-qD-0Kul2.js +1 -0
  103. package/dist/assets/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  104. package/dist/assets/web/dist/assets/dart-CnvKMtbv.js +1 -0
  105. package/dist/assets/web/dist/assets/dax-BkyTk9wS.js +1 -0
  106. package/dist/assets/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  107. package/dist/assets/web/dist/assets/diff-woXpYk--.js +1 -0
  108. package/dist/assets/web/dist/assets/dist-BVjAWgkb.js +1547 -0
  109. package/dist/assets/web/dist/assets/docker-IyjqRm3v.js +1 -0
  110. package/dist/assets/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  111. package/dist/assets/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  112. package/dist/assets/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  113. package/dist/assets/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  114. package/dist/assets/web/dist/assets/edge-BenMDPct.js +1 -0
  115. package/dist/assets/web/dist/assets/elixir-CBbdEqG6.js +1 -0
  116. package/dist/assets/web/dist/assets/elm-DMub_2lY.js +1 -0
  117. package/dist/assets/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  118. package/dist/assets/web/dist/assets/erb-CI9He_Up.js +1 -0
  119. package/dist/assets/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  120. package/dist/assets/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  121. package/dist/assets/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  122. package/dist/assets/web/dist/assets/{favicon-CFfbl2AI.js → favicon-CWPcvWvp.js} +1 -1
  123. package/dist/assets/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  124. package/dist/assets/web/dist/assets/fish-BJitypiv.js +1 -0
  125. package/dist/assets/web/dist/assets/fluent-C03EYrpw.js +1 -0
  126. package/dist/assets/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  127. package/dist/assets/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  128. package/dist/assets/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  129. package/dist/assets/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  130. package/dist/assets/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  131. package/dist/assets/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  132. package/dist/assets/web/dist/assets/genie-CV2tkWYe.js +1 -0
  133. package/dist/assets/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  134. package/dist/assets/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  135. package/dist/assets/web/dist/assets/git-rebase-BBfhnyF8.js +1 -0
  136. package/dist/assets/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  137. package/dist/assets/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  138. package/dist/assets/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  139. package/dist/assets/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  140. package/dist/assets/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  141. package/dist/assets/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  142. package/dist/assets/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  143. package/dist/assets/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  144. package/dist/assets/web/dist/assets/glimmer-js-CR9Fxrau.js +1 -0
  145. package/dist/assets/web/dist/assets/glimmer-ts-CwFwX_XS.js +1 -0
  146. package/dist/assets/web/dist/assets/glsl-Daiodp1r.js +1 -0
  147. package/dist/assets/web/dist/assets/gn-ilITqXS6.js +1 -0
  148. package/dist/assets/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  149. package/dist/assets/web/dist/assets/go-rLFTqkRN.js +1 -0
  150. package/dist/assets/web/dist/assets/graphql-DZvrsEkB.js +1 -0
  151. package/dist/assets/web/dist/assets/groovy-CacY0gHj.js +1 -0
  152. package/dist/assets/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  153. package/dist/assets/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  154. package/dist/assets/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  155. package/dist/assets/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  156. package/dist/assets/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  157. package/dist/assets/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  158. package/dist/assets/web/dist/assets/hack-BaXOzdg7.js +1 -0
  159. package/dist/assets/web/dist/assets/haml-CMskq_VA.js +1 -0
  160. package/dist/assets/web/dist/assets/handlebars-Dhw_f7jD.js +1 -0
  161. package/dist/assets/web/dist/assets/haskell-D8IpX4py.js +1 -0
  162. package/dist/assets/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  163. package/dist/assets/web/dist/assets/hcl-Dh228itO.js +1 -0
  164. package/dist/assets/web/dist/assets/hjson-CxZEssPk.js +1 -0
  165. package/dist/assets/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  166. package/dist/assets/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  167. package/dist/assets/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  168. package/dist/assets/web/dist/assets/houston-CsvMBhTu.js +1 -0
  169. package/dist/assets/web/dist/assets/html-C9k99z0z.js +1 -0
  170. package/dist/assets/web/dist/assets/html-derivative-VckPItXN.js +1 -0
  171. package/dist/assets/web/dist/assets/http-DXU_3h9v.js +1 -0
  172. package/dist/assets/web/dist/assets/hurl-KU24JNdp.js +1 -0
  173. package/dist/assets/web/dist/assets/hxml-Dcm88srW.js +1 -0
  174. package/dist/assets/web/dist/assets/hy-CZbG8q4J.js +1 -0
  175. package/dist/assets/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  176. package/dist/assets/web/dist/assets/indexFormat-B-pd4r7Y.js +1 -0
  177. package/dist/assets/web/dist/assets/ini-B5eOa1yu.js +1 -0
  178. package/dist/assets/web/dist/assets/java-BbZjTNgf.js +1 -0
  179. package/dist/assets/web/dist/assets/javascript-DXCdqcZZ.js +1 -0
  180. package/dist/assets/web/dist/assets/jinja-pDlsjTVp.js +1 -0
  181. package/dist/assets/web/dist/assets/jison-DsXDk66e.js +1 -0
  182. package/dist/assets/web/dist/assets/json-tCxXfRgG.js +1 -0
  183. package/dist/assets/web/dist/assets/json5-BR5RXkoi.js +1 -0
  184. package/dist/assets/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  185. package/dist/assets/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  186. package/dist/assets/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  187. package/dist/assets/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  188. package/dist/assets/web/dist/assets/jsx-B0ZYkCmi.js +1 -0
  189. package/dist/assets/web/dist/assets/julia-BLzzeI6x.js +1 -0
  190. package/dist/assets/web/dist/assets/just-DvD2_60c.js +1 -0
  191. package/dist/assets/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  192. package/dist/assets/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  193. package/dist/assets/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  194. package/dist/assets/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  195. package/dist/assets/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  196. package/dist/assets/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  197. package/dist/assets/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  198. package/dist/assets/web/dist/assets/latex-511zn3h7.js +1 -0
  199. package/dist/assets/web/dist/assets/lean-CewbzKMR.js +1 -0
  200. package/dist/assets/web/dist/assets/less-DVTAwKKz.js +1 -0
  201. package/dist/assets/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  202. package/dist/assets/web/dist/assets/liquid-BD0BkMox.js +1 -0
  203. package/dist/assets/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  204. package/dist/assets/web/dist/assets/log-BNLmms1o.js +1 -0
  205. package/dist/assets/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  206. package/dist/assets/web/dist/assets/lua-NFicKm6N.js +1 -0
  207. package/dist/assets/web/dist/assets/luau-C2pBCSgt.js +1 -0
  208. package/dist/assets/web/dist/assets/main-CuENKPdD.css +1 -0
  209. package/dist/assets/web/dist/assets/main-DZbJaqUb.js +28 -0
  210. package/dist/assets/web/dist/assets/make-Dixweg8N.js +1 -0
  211. package/dist/assets/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  212. package/dist/assets/web/dist/assets/marko-1-G_nEXK.js +1 -0
  213. package/dist/assets/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  214. package/dist/assets/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  215. package/dist/assets/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  216. package/dist/assets/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  217. package/dist/assets/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  218. package/dist/assets/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  219. package/dist/assets/web/dist/assets/mdc-DUe3AQLp.js +1 -0
  220. package/dist/assets/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  221. package/dist/assets/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  222. package/dist/assets/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  223. package/dist/assets/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  224. package/dist/assets/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  225. package/dist/assets/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  226. package/dist/assets/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
  227. package/dist/assets/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
  228. package/dist/assets/web/dist/assets/move-BYnGhZoM.js +1 -0
  229. package/dist/assets/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  230. package/dist/assets/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  231. package/dist/assets/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  232. package/dist/assets/web/dist/assets/nginx-Dg379_bA.js +1 -0
  233. package/dist/assets/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  234. package/dist/assets/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  235. package/dist/assets/web/dist/assets/nim-BnFq97ZO.js +1 -0
  236. package/dist/assets/web/dist/assets/nix-IvuFDN5E.js +1 -0
  237. package/dist/assets/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  238. package/dist/assets/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  239. package/dist/assets/web/dist/assets/nushell-DHYfO66s.js +1 -0
  240. package/dist/assets/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  241. package/dist/assets/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  242. package/dist/assets/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  243. package/dist/assets/web/dist/assets/odin-B1RWQWA5.js +1 -0
  244. package/dist/assets/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  245. package/dist/assets/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  246. package/dist/assets/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  247. package/dist/assets/web/dist/assets/org-F0uiwGvq.js +1 -0
  248. package/dist/assets/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  249. package/dist/assets/web/dist/assets/perl-B0euczkl.js +1 -0
  250. package/dist/assets/web/dist/assets/php-DkL3k_n7.js +1 -0
  251. package/dist/assets/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  252. package/dist/assets/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  253. package/dist/assets/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  254. package/dist/assets/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  255. package/dist/assets/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  256. package/dist/assets/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  257. package/dist/assets/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  258. package/dist/assets/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  259. package/dist/assets/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  260. package/dist/assets/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  261. package/dist/assets/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  262. package/dist/assets/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  263. package/dist/assets/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  264. package/dist/assets/web/dist/assets/po-BiJDBrnU.js +1 -0
  265. package/dist/assets/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  266. package/dist/assets/web/dist/assets/polar-C7UOKdEL.js +1 -0
  267. package/dist/assets/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  268. package/dist/assets/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  269. package/dist/assets/web/dist/assets/powershell-CCSX75yM.js +1 -0
  270. package/dist/assets/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  271. package/dist/assets/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  272. package/dist/assets/web/dist/assets/proto-DB4EqR-F.js +1 -0
  273. package/dist/assets/web/dist/assets/pug-L_OjjZP4.js +1 -0
  274. package/dist/assets/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  275. package/dist/assets/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  276. package/dist/assets/web/dist/assets/python-gzcpVVnB.js +1 -0
  277. package/dist/assets/web/dist/assets/qml-BMjB00Zz.js +1 -0
  278. package/dist/assets/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  279. package/dist/assets/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  280. package/dist/assets/web/dist/assets/r-DoeLdnqR.js +1 -0
  281. package/dist/assets/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  282. package/dist/assets/web/dist/assets/raku-B3gFvitq.js +1 -0
  283. package/dist/assets/web/dist/assets/razor-BzYNkAWy.js +1 -0
  284. package/dist/assets/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  285. package/dist/assets/web/dist/assets/red-DX1BQOs8.js +1 -0
  286. package/dist/assets/web/dist/assets/reg-CRGYupPL.js +1 -0
  287. package/dist/assets/web/dist/assets/regexp-CPmElMk3.js +1 -0
  288. package/dist/assets/web/dist/assets/rel-BtDbiS_P.js +1 -0
  289. package/dist/assets/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  290. package/dist/assets/web/dist/assets/ron-VUp2lXgN.js +1 -0
  291. package/dist/assets/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  292. package/dist/assets/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  293. package/dist/assets/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  294. package/dist/assets/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  295. package/dist/assets/web/dist/assets/rst-DrsjgfI7.js +1 -0
  296. package/dist/assets/web/dist/assets/ruby-cB42ppvx.js +1 -0
  297. package/dist/assets/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  298. package/dist/assets/web/dist/assets/sas-R5Y3NM_I.js +1 -0
  299. package/dist/assets/web/dist/assets/sass-DXrisJhu.js +1 -0
  300. package/dist/assets/web/dist/assets/scala-BiFt_7mu.js +1 -0
  301. package/dist/assets/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  302. package/dist/assets/web/dist/assets/scss-C-zWxV9s.js +1 -0
  303. package/dist/assets/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  304. package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +6 -0
  305. package/dist/assets/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  306. package/dist/assets/web/dist/assets/shellscript-oJF96aAU.js +1 -0
  307. package/dist/assets/web/dist/assets/shellsession-CznECKyi.js +1 -0
  308. package/dist/assets/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  309. package/dist/assets/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  310. package/dist/assets/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  311. package/dist/assets/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  312. package/dist/assets/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  313. package/dist/assets/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  314. package/dist/assets/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  315. package/dist/assets/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  316. package/dist/assets/web/dist/assets/soy-wjHLSRag.js +1 -0
  317. package/dist/assets/web/dist/assets/sparql-D_iOobhT.js +1 -0
  318. package/dist/assets/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  319. package/dist/assets/web/dist/assets/sql-C_BM8IOW.js +1 -0
  320. package/dist/assets/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  321. package/dist/assets/web/dist/assets/stata-D31HO8aQ.js +1 -0
  322. package/dist/assets/web/dist/assets/stylus-B6D30XZt.js +1 -0
  323. package/dist/assets/web/dist/assets/surrealql-CKCLyplA.js +1 -0
  324. package/dist/assets/web/dist/assets/svelte-3geWk2iu.js +1 -0
  325. package/dist/assets/web/dist/assets/swift-CyEgAFGc.js +1 -0
  326. package/dist/assets/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  327. package/dist/assets/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  328. package/dist/assets/web/dist/assets/systemd-BxMlprV5.js +1 -0
  329. package/dist/assets/web/dist/assets/talonscript-CohzipZa.js +1 -0
  330. package/dist/assets/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  331. package/dist/assets/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  332. package/dist/assets/web/dist/assets/templ-DiJr_hTD.js +1 -0
  333. package/dist/assets/web/dist/assets/terraform-DswuEJGm.js +1 -0
  334. package/dist/assets/web/dist/assets/tex-BEJGWyeF.js +1 -0
  335. package/dist/assets/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  336. package/dist/assets/web/dist/assets/toml-CcmNWLt0.js +1 -0
  337. package/dist/assets/web/dist/assets/ts-tags-C8Mzdpxx.js +1 -0
  338. package/dist/assets/web/dist/assets/tsv-sltzmVWM.js +1 -0
  339. package/dist/assets/web/dist/assets/tsx-UQiSm_p3.js +1 -0
  340. package/dist/assets/web/dist/assets/turtle-ByJddavk.js +1 -0
  341. package/dist/assets/web/dist/assets/twig-_Pvzkeo9.js +1 -0
  342. package/dist/assets/web/dist/assets/typescript-BWMkINKK.js +1 -0
  343. package/dist/assets/web/dist/assets/typespec-BRdr0IET.js +1 -0
  344. package/dist/assets/web/dist/assets/typst-Bdg9m-e7.js +1 -0
  345. package/dist/assets/web/dist/assets/v-DBj4K9d_.js +1 -0
  346. package/dist/assets/web/dist/assets/vala-zf12oZj6.js +1 -0
  347. package/dist/assets/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  348. package/dist/assets/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  349. package/dist/assets/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  350. package/dist/assets/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  351. package/dist/assets/web/dist/assets/viml-DvXPmvsu.js +1 -0
  352. package/dist/assets/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  353. package/dist/assets/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  354. package/dist/assets/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  355. package/dist/assets/web/dist/assets/vue-CDZrrAZ8.js +1 -0
  356. package/dist/assets/web/dist/assets/vue-html-tAlpNhfN.js +1 -0
  357. package/dist/assets/web/dist/assets/vue-vine-C27UF_rh.js +1 -0
  358. package/dist/assets/web/dist/assets/vyper-CgoNMtux.js +1 -0
  359. package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +1 -0
  360. package/dist/assets/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  361. package/dist/assets/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  362. package/dist/assets/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  363. package/dist/assets/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  364. package/dist/assets/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  365. package/dist/assets/web/dist/assets/wit-DdvCle-K.js +1 -0
  366. package/dist/assets/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  367. package/dist/assets/web/dist/assets/xml-CxeDr9Zh.js +1 -0
  368. package/dist/assets/web/dist/assets/xsl-Bpi0uUnM.js +1 -0
  369. package/dist/assets/web/dist/assets/yaml-BtUgZ1GN.js +1 -0
  370. package/dist/assets/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  371. package/dist/assets/web/dist/assets/zig-CMLA9XwU.js +1 -0
  372. package/dist/cli.js +6936 -3347
  373. package/package.json +1 -1
  374. package/dist/assets/web/dist/assets/AppShell-Bw3-_TTE.js +0 -1
  375. package/dist/assets/web/dist/assets/DeliveryPage-BRwcQyr7.js +0 -1
  376. package/dist/assets/web/dist/assets/NotFoundPage-RjH-9dPy.js +0 -1
  377. package/dist/assets/web/dist/assets/ResidentDetailPage-BRy5wkv9.js +0 -1
  378. package/dist/assets/web/dist/assets/ResidentsIndexPage-DAEVLN8j.js +0 -1
  379. package/dist/assets/web/dist/assets/RunRoutePage-B1KHmkZ9.js +0 -126
  380. package/dist/assets/web/dist/assets/RunRoutePage-CdbrbqU2.css +0 -1
  381. package/dist/assets/web/dist/assets/RunsIndexPage-3hUWFpFV.js +0 -1
  382. package/dist/assets/web/dist/assets/RunsTabs-Qn_5TVJU.js +0 -1
  383. package/dist/assets/web/dist/assets/ScheduledPage-DysVvVm1.js +0 -1
  384. package/dist/assets/web/dist/assets/StatusDot-Bug2a6T6.js +0 -1
  385. package/dist/assets/web/dist/assets/Tooltip-C2eEUbwn.js +0 -1
  386. package/dist/assets/web/dist/assets/main-0uhp-taL.js +0 -28
  387. package/dist/assets/web/dist/assets/main-Xickfv8V.css +0 -1
  388. package/dist/assets/web/dist/assets/seed-B8mQEhhw.js +0 -6
  389. package/dist/assets/web/dist/assets/wallClock-DI4HIEN5.js +0 -1
@@ -1,7 +1,10 @@
1
1
  # Switchboard — single-process Slack bot (Socket Mode, no inbound port needed).
2
2
  # git + gh are installed because the coding/review agents shell out to them.
3
3
 
4
- FROM node:24-slim AS build
4
+ # Node at the exact tag every image in this repository shares (the execution
5
+ # images copy theirs from the same one); its major is .nvmrc's, and
6
+ # src/deploy/imageNode.test.ts holds both.
7
+ FROM node:24.21.0-slim AS build
5
8
  WORKDIR /app
6
9
  # One lockfile covers every workspace. npm needs each workspace's manifest on
7
10
  # disk to resolve the tree, so the manifests are copied before the install;
@@ -28,7 +31,7 @@ RUN npm run build
28
31
  # Runtime dependencies alone: the bot's production dependencies, no dev tools,
29
32
  # no web toolchain — a clean install rather than a prune, so nothing hoisted
30
33
  # for the build survives into the image.
31
- FROM node:24-slim AS deps
34
+ FROM node:24.21.0-slim AS deps
32
35
  WORKDIR /app
33
36
  COPY package.json package-lock.json ./
34
37
  COPY web/package.json ./web/
@@ -53,7 +56,7 @@ RUN npm ci --omit=dev --workspaces=false --include-workspace-root
53
56
  FROM docker.io/library/golang:1.27.1-bookworm AS meat
54
57
  RUN CGO_ENABLED=0 go install meat.dev/cmd/meat@f39f41dfe7b5b37a12b35fdfbaecc7e779855bd3
55
58
 
56
- FROM node:24-slim
59
+ FROM node:24.21.0-slim
57
60
  RUN apt-get update \
58
61
  && apt-get install -y --no-install-recommends git curl ca-certificates \
59
62
  && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
@@ -41,6 +41,7 @@ defaults:
41
41
  general: anthropic/claude-haiku-4-5
42
42
  coding: anthropic/claude-opus-5
43
43
  review: anthropic/claude-opus-5
44
+ explore: anthropic/claude-opus-5 # the two-hour read-only investigation (docs/reference/specs/agent-explore.md)
44
45
  # Default model effort per agent: low | medium | high (how hard the model
45
46
  # thinks per turn; lower = much faster turns). Same layering as models —
46
47
  # per-channel/user `effort` / `efforts.<agent>` and a per-request `effort:`
@@ -48,6 +49,21 @@ defaults:
48
49
  # provider default. Skipped for models without effort support.
49
50
  # efforts:
50
51
  # coding: medium
52
+ # A boundary caps what ANY run in a scope may have — never grants — on the
53
+ # three axes of a run's profile (docs/reference/specs/routing-and-config.md item 2):
54
+ # maxMinutes (the wall-clock budget; at least 2), maxIdentity (the credential
55
+ # a run acts as: none < read < write) and machines (the classes its tools may
56
+ # execute on: none, blank, repo-cold, repo-resident). Unlike every other
57
+ # setting, boundaries INTERSECT across defaults, channel and user — the
58
+ # smallest budget, the lowest identity, the classes every layer allows — so a
59
+ # channel or a user can only tighten what is set here. A budget above a cap is
60
+ # clipped (the card and the run record say so); a preset whose identity or
61
+ # class is above a cap is refused by name before any executor exists. Absent →
62
+ # nothing is capped and every preset runs exactly its declared profile.
63
+ # boundary:
64
+ # maxMinutes: 120
65
+ # maxIdentity: write
66
+ # machines: [none, blank, repo-cold, repo-resident]
51
67
 
52
68
  # PR reviews and the reading diff (docs/reference/specs/reading-diff.md). Every
53
69
  # review records its full `git diff`; the ABRIDGED version (meat.dev, run on
@@ -72,6 +88,12 @@ defaults:
72
88
  # review: anthropic/claude-opus-5
73
89
  # efforts:
74
90
  # coding: medium
91
+ # # Cap this channel's runs (see defaults.boundary): here every run acts
92
+ # # with at most a read credential — `agent:coding` is refused by name,
93
+ # # `agent:review` runs — and no run gets more than 45 minutes.
94
+ # boundary:
95
+ # maxMinutes: 45
96
+ # maxIdentity: read
75
97
 
76
98
  # Static per-user defaults (user ID -> scope).
77
99
  # users:
@@ -87,7 +109,7 @@ defaults:
87
109
  # bypassed via directives or config scopes. Names must be registered agents /
88
110
  # owner/name slugs, or the load fails.
89
111
  # restrict:
90
- # agents: [coding]
112
+ # agents: [coding, conductor] # conductor: fan-out closed unless granted (docs/reference/specs/agent-conductor.md)
91
113
  # repos: [acme/api]
92
114
  #
93
115
  # `grants`: what ANY actor holds, in one shape, keyed by its platform-namespaced
@@ -135,6 +157,16 @@ defaults:
135
157
  # http:cron: # the identity the shim fires schedules as
136
158
  # actions: [friction:read, friction:write, repo:write]
137
159
  # channels: all # the weekly pass analyzes the whole fleet
160
+ # http:coordinator: # the ship coordinator's bearer (its own
161
+ # actions: [coordinator:step, plan:merge] # SWITCHBOARD_INGRESS_TOKENS entry, subject
162
+ # # "coordinator", rotated apart from cron):
163
+ # # coordinator:step is the one grant the
164
+ # # coordinator's steps need — the child a spawn
165
+ # # starts is authorized as the requesting user,
166
+ # # never as this identity; plan:merge lets the
167
+ # # plan runner merge a plan branch's pull request
168
+ # # once the review approved at its head and CI is
169
+ # # green — leave it out and every merge is a person's
138
170
  # schedule:self-improvement: # the weekly cron's own actor (src/core/schedules.ts
139
171
  # actions: [friction:write] # declares its grants; an entry here REPLACES them)
140
172
  # channels: all # analyzes the whole fleet, not one channel
@@ -249,10 +281,26 @@ workspaceDir: ./workspaces
249
281
  # <task>` runs the coding → review → fix loop to LGTM as one pipeline: at most
250
282
  # `maxRounds` review rounds and `maxMinutes` minutes of wall clock — whichever
251
283
  # hits first ends the loop, and each child round's own budget is clipped to the
252
- # remaining pipeline time. Defaults shown; both must be integers >= 1.
284
+ # remaining pipeline time. `maxMinutes` is the ship preset's declared budget:
285
+ # a channel or user `boundary.maxMinutes` and a per-message `budget:` directive
286
+ # clip it like any preset's, and the card says so. Defaults shown; both must be
287
+ # integers >= 1.
253
288
  # ship:
254
289
  # maxRounds: 3 # review rounds per pipeline
255
- # maxMinutes: 120 # pipeline wall-clock budget in minutes
290
+ # maxMinutes: 120 # the ship preset's wall-clock budget in minutes (the registry's default)
291
+ # coordinator: false # true hands every agent:ship request to the plan runner — the ShipCoordinator
292
+ # # Workflow in the bot Worker — instead of the in-process round loop; needs the
293
+ # # `coordinator` ingress entry, its grant, run history on the state Worker and
294
+ # # PUBLIC_BASE_URL (see docs/how-to/turn-features-on-and-off.md)
295
+
296
+ # The fan-out cap a spawning run meets (docs/reference/specs/agent-conductor.md).
297
+ # `agent:conductor` starts child runs as the person who asked — each an
298
+ # ordinary run in a thread of its own, under that person's permissions — and
299
+ # at most `maxChildren` of one run's children are live at once: a spawn past
300
+ # the cap is refused by name until a child finishes. A child cannot spawn.
301
+ # Default shown; an integer >= 1.
302
+ # spawn:
303
+ # maxChildren: 3
256
304
 
257
305
  # Spend reporting: GET /costs (Access-gated). Optional. See docs/reference/specs/costs.md.
258
306
  # costs:
@@ -273,7 +321,7 @@ workspaceDir: ./workspaces
273
321
  # anthropicWorkspaceId: wrkspc_... # this app's Anthropic workspace
274
322
 
275
323
  # Delivery indicators: GET /delivery and `delivery report` (docs/reference/specs/delivery.md).
276
- # Optional. Needs a GitHub credential (the App triple or GH_TOKEN); read-only.
324
+ # Optional. Needs a GitHub credential (the App triple or GH_TOKEN); read-only on GitHub.
277
325
  # delivery:
278
326
  # repos: [acme/api, acme/web] # the repositories the page serves; the first is its default
279
327
  # # (`delivery report --repo owner/name` needs no entry here)
@@ -281,6 +329,10 @@ workspaceDir: ./workspaces
281
329
  # # beside this installation's own App identity
282
330
  # agentLogins: [] # logins that are agents beyond the `[bot]` suffix
283
331
  # agentCoauthors: [Claude] # `Co-Authored-By:` names that mark a commit as an agent's
332
+ # snapshot: # each repository's facts are read from GitHub on an interval and
333
+ # everyMinutes: 60 # served from that snapshot (its age on the page; `?fresh=1` /
334
+ # # `--fresh` read now); 5–1440 minutes, default 60. Kept on the
335
+ # # state Worker any `*.worker` block below names; in memory without one
284
336
 
285
337
  # Dashboard authentication (docs/reference/specs/access-gate.md). Everything the dashboard
286
338
  # serves — /runs*, /residents*, /costs*, /mcp/connect/*, and every /api/* command
@@ -0,0 +1,86 @@
1
+ // The ship coordinator as a Workflow (docs/decisions/0029-durable-objects-store-workflows-schedule.md,
2
+ // docs/decisions/0031-the-coordinator-runs-a-plan-not-a-pull-request.md;
3
+ // docs/reference/specs/http-ingress.md item 9): the `ShipCoordinator`
4
+ // entrypoint the Workflows binding names, declared here and re-exported by
5
+ // worker.ts (the binding resolves `class_name` against the entry module, the
6
+ // way the resident Worker's refresh cycle is split). `run()` is the plan
7
+ // runner's driver (`src/core/coordinator/driver.ts`) over the platform's step
8
+ // primitives and one bot client: every step is a `POST /admin/coordinator/<step>`
9
+ // into the container, presented with the `coordinator` bearer from the token
10
+ // map, and every fact and every child run is the bot's to produce. The instance
11
+ // id is the plan's (`plan-<plan-id>`, or the ship record's for a task): the
12
+ // bot wrote the instance and its unit rows before it created the instance, and
13
+ // the driver's first step reads them back — nothing rides in the params.
14
+ //
15
+ // What a coordinator may hold is bounded here by type and in coordinator.test.ts
16
+ // by a scan: the container binding (its loopback into the bot) and the token
17
+ // map (the `coordinator` entry it presents to the bot's steps) — no model,
18
+ // Slack or GitHub credential, and no fetch but the container binding's.
19
+ import { getContainer } from "@cloudflare/containers";
20
+ import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from "cloudflare:workers";
21
+ import { NonRetryableError } from "cloudflare:workflows";
22
+ import { COORDINATOR_IDENTITY, COORDINATOR_STEP_PATH_PREFIX } from "../../src/core/coordinator/contract.ts";
23
+ import {
24
+ readBotAnswer,
25
+ runPlan,
26
+ type CoordinatorBot,
27
+ type PlanRunSummary,
28
+ type StepRunner,
29
+ } from "../../src/core/coordinator/driver.ts";
30
+ import { parseIngressTokenMap, tokenForSubject } from "../../src/core/ingressTokens.ts";
31
+ import { INSTANCE, INTERNAL } from "./shared";
32
+ import type { Env } from "./worker";
33
+
34
+ /** The env slice a coordinator instance may see. Everything else the shim
35
+ * holds for the container is out of the coordinator's reach by type. */
36
+ export type CoordinatorEnv = Pick<Env, "SWITCHBOARD" | "SWITCHBOARD_INGRESS_TOKENS">;
37
+
38
+ /** What an instance is created with: nothing the driver reads — the instance
39
+ * id names the plan, and the bot's rows are the input (the coordinator's
40
+ * contract: ids only, never a task's text or a thread's contents). */
41
+ export type ShipCoordinatorParams = Record<string, unknown>;
42
+
43
+ /** The bot behind the container binding: the reply as the wire carried it.
44
+ * The transport's failures throw for the step's retry; the door's own refusal
45
+ * — a 401/403 the bot did not stamp, a bearer it does not admit — is final,
46
+ * since no retry changes a token map. */
47
+ function containerBot(env: CoordinatorEnv): CoordinatorBot {
48
+ return {
49
+ async step(route, body) {
50
+ const bearer = tokenForSubject(parseIngressTokenMap(env.SWITCHBOARD_INGRESS_TOKENS).tokens, COORDINATOR_IDENTITY);
51
+ if (bearer === undefined)
52
+ throw new NonRetryableError(
53
+ `the token map has no single \`${COORDINATOR_IDENTITY}\` entry — the coordinator cannot present itself to the bot`,
54
+ );
55
+ const res = await getContainer(env.SWITCHBOARD, INSTANCE).fetch(
56
+ new Request(`${INTERNAL}${COORDINATOR_STEP_PATH_PREFIX}${route}`, {
57
+ method: "POST",
58
+ headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
59
+ body: JSON.stringify(body),
60
+ }),
61
+ );
62
+ const text = await res.text().catch(() => "");
63
+ if (res.status === 401 || res.status === 403) {
64
+ const read = readBotAnswer(res.status, text);
65
+ if (!read.ok) throw new NonRetryableError(`the bot refused the coordinator bearer on ${route}: ${read.reason}`);
66
+ }
67
+ return { status: res.status, text };
68
+ },
69
+ };
70
+ }
71
+
72
+ /** The platform's step as the driver types it. Every stored step output is a
73
+ * bot answer — a JSON object — so the platform's serializable bound holds. */
74
+ function workflowSteps(step: WorkflowStep): StepRunner {
75
+ return {
76
+ do: (name, config, callback) => step.do(name, config, callback),
77
+ sleep: (name, ms) => step.sleep(name, ms),
78
+ waitForEvent: (name, options) => step.waitForEvent(name, options),
79
+ };
80
+ }
81
+
82
+ export class ShipCoordinator extends WorkflowEntrypoint<CoordinatorEnv, ShipCoordinatorParams> {
83
+ async run(event: Readonly<WorkflowEvent<ShipCoordinatorParams>>, step: WorkflowStep): Promise<PlanRunSummary> {
84
+ return runPlan(workflowSteps(step), containerBot(this.env), event.instanceId);
85
+ }
86
+ }
@@ -0,0 +1,9 @@
1
+ // What the shim's entry and its Workflow both address: the one container
2
+ // instance the shim runs (a singleton Durable Object) and the internal origin
3
+ // every request into it is written against — the container ignores the host,
4
+ // the origin only has to be a URL. Declared apart from worker.ts so
5
+ // coordinator.ts reaches them without importing the entry (the binding
6
+ // resolves the Workflow's class against worker.ts, which re-exports it: a value
7
+ // import the other way would be a cycle).
8
+ export const INSTANCE = "singleton";
9
+ export const INTERNAL = "https://switchboard-keepalive.internal";
@@ -12,6 +12,18 @@
12
12
  import { Container, getContainer } from "@cloudflare/containers";
13
13
  import { parseHealthz } from "../../src/deploy/liveGate.ts";
14
14
  import {
15
+ COORDINATOR_AUTHORIZE_PATH,
16
+ COORDINATOR_INSTANCES_PATH,
17
+ createInstanceResponse,
18
+ instanceStatusResponse,
19
+ isInstanceNotFound,
20
+ parseInstanceStatusPath,
21
+ parseCreateInstanceRequest,
22
+ parseSubjectAuthorization,
23
+ type CreateInstanceOutcome,
24
+ } from "../../src/core/coordinator/instancesRoute.ts";
25
+ import {
26
+ authenticateIngressBearer,
15
27
  authenticateRestart,
16
28
  decideRestart,
17
29
  parseRestartAuthorization,
@@ -34,6 +46,13 @@ import {
34
46
  import { systemClock } from "../../src/core/trace/clock.ts";
35
47
  import { createTracer } from "../../src/core/trace/tracer.ts";
36
48
  import { shimRoute, stripTraceContext, withTraceContext, workerLogSink } from "../../src/core/trace/workerTrace.ts";
49
+ import type { ShipCoordinatorParams } from "./coordinator";
50
+ import { INSTANCE, INTERNAL } from "./shared";
51
+
52
+ /** The ship coordinator's Workflow entrypoint is declared in coordinator.ts;
53
+ * the Workflows binding resolves its `class_name` against this module
54
+ * (wrangler.template.jsonc), so the entry exports it under that name. */
55
+ export { ShipCoordinator } from "./coordinator";
37
56
 
38
57
  // The Worker's own spans (docs/reference/specs/tracing.md item 22): one `bot-shim.fetch`
39
58
  // root per routed request and one `cron.<schedule>` root per fired schedule,
@@ -42,8 +61,11 @@ import { shimRoute, stripTraceContext, withTraceContext, workerLogSink } from ".
42
61
  const tracer = createTracer({ clock: systemClock });
43
62
  const traceSinks = [workerLogSink((line) => console.log(line))];
44
63
 
45
- interface Env {
64
+ export interface Env {
46
65
  SWITCHBOARD: DurableObjectNamespace<SwitchboardServer>;
66
+ /** The ship coordinator (coordinator.ts): `POST /admin/coordinator/instances`
67
+ * creates its instances; the state Worker's finish sends them `run finished`. */
68
+ SHIP_COORDINATOR: Workflow<ShipCoordinatorParams>;
47
69
  // secrets (wrangler secret put ...)
48
70
  SLACK_BOT_TOKEN: string;
49
71
  SLACK_APP_TOKEN: string;
@@ -117,9 +139,6 @@ function containerEnv(env: Env): Record<string, string> {
117
139
  return vars;
118
140
  }
119
141
 
120
- const INSTANCE = "singleton";
121
- const INTERNAL = "https://switchboard-keepalive.internal";
122
-
123
142
  export class SwitchboardServer extends Container<Env> {
124
143
  defaultPort = 8080; // the bot's health endpoint (PORT=8080 in the image)
125
144
  // Never let this scale to zero: the Slack websocket must stay connected and
@@ -260,6 +279,116 @@ async function handleAdminRestart(request: Request, env: Env): Promise<Response>
260
279
  return json(res.status, res.body);
261
280
  }
262
281
 
282
+ /** `POST /admin/coordinator/instances` — the one route that creates a ship
283
+ * coordinator instance (docs/reference/specs/http-ingress.md item 9), the
284
+ * Workflow being this Worker's. WHO: the bearer must be an entry of
285
+ * SWITCHBOARD_INGRESS_TOKENS (401/503 here, an unknown bearer never touches
286
+ * the container). WHETHER: the bot's config — the shim relays the bearer to
287
+ * `POST /admin/coordinator/authorize`, where the policy table decides
288
+ * `coordinator:step`, and creates only on a 200. Body `{ id, params? }`. */
289
+ async function handleCoordinatorInstances(request: Request, env: Env): Promise<Response> {
290
+ const json = (status: number, body: Record<string, unknown>) =>
291
+ new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
292
+ if (request.method !== "POST")
293
+ return json(405, { ok: false, error: `method not allowed: POST ${COORDINATOR_INSTANCES_PATH}` });
294
+ const authorization = request.headers.get("authorization") ?? undefined;
295
+ const authn = authenticateIngressBearer(authorization, env.SWITCHBOARD_INGRESS_TOKENS, "coordinator");
296
+ if (!authn.ok) {
297
+ console.warn(`[coordinator] instances ${authn.status} — ${authn.reason}`);
298
+ return json(authn.status, { ok: false, error: authn.reason });
299
+ }
300
+ const parsed = parseCreateInstanceRequest(await request.text().catch(() => ""));
301
+ if (!parsed.ok) return json(400, { ok: false, error: parsed.reason });
302
+ let answer: Response;
303
+ try {
304
+ answer = await getContainer(env.SWITCHBOARD, INSTANCE).fetch(
305
+ new Request(`${INTERNAL}${COORDINATOR_AUTHORIZE_PATH}`, {
306
+ method: "POST",
307
+ headers: { authorization: authorization ?? "" },
308
+ }),
309
+ );
310
+ } catch (err) {
311
+ const reason = err instanceof Error ? err.message : String(err);
312
+ console.error(`[coordinator] instances ${parsed.id}: the bot could not be asked: ${reason}`);
313
+ return json(503, { ok: false, error: `coordinator disabled: the bot could not be asked (${reason})` });
314
+ }
315
+ const auth = parseSubjectAuthorization(answer.status, await answer.text().catch(() => ""));
316
+ if (!auth.ok) {
317
+ console.warn(`[coordinator] instances ${auth.status} — ${auth.reason}`);
318
+ return json(auth.status, { ok: false, error: auth.reason });
319
+ }
320
+ let outcome: CreateInstanceOutcome;
321
+ try {
322
+ await env.SHIP_COORDINATOR.create({ id: parsed.id, params: parsed.params });
323
+ outcome = { kind: "created", id: parsed.id };
324
+ } catch (err) {
325
+ // The engine refuses a taken id; read the existing instance back to say
326
+ // so. Anything else — the engine unreachable, a malformed param — is a
327
+ // failure by reason.
328
+ const reason = err instanceof Error ? err.message : String(err);
329
+ try {
330
+ const existing = await (await env.SHIP_COORDINATOR.get(parsed.id)).status();
331
+ outcome = { kind: "duplicate", id: parsed.id, status: existing.status };
332
+ } catch {
333
+ outcome = { kind: "failed", id: parsed.id, reason };
334
+ }
335
+ }
336
+ console.log(`[coordinator] ${auth.subject} → instance ${parsed.id}: ${outcome.kind}`);
337
+ const res = createInstanceResponse(outcome);
338
+ return json(res.status, res.body);
339
+ }
340
+
341
+ /** `GET /admin/coordinator/instances/<id>` — the platform's status of one
342
+ * instance (docs/reference/specs/http-ingress.md item 9), read by the bot
343
+ * before a plan is re-issued: still running, ended, or never created (the
344
+ * leftover of a create that failed). The same door as the create: the bearer
345
+ * in the map, the bot's `authorize` answer. The engine's own `instance.not_found`
346
+ * is 404; any other failure is 502 by reason — never read as absence, because a
347
+ * wrong absence would let a re-issue write over a live runner's records. */
348
+ async function handleCoordinatorInstanceStatus(request: Request, env: Env, id: string): Promise<Response> {
349
+ const json = (status: number, body: Record<string, unknown>) =>
350
+ new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
351
+ if (request.method !== "GET")
352
+ return json(405, {
353
+ ok: false,
354
+ error: `method not allowed: ${request.method} — ${COORDINATOR_INSTANCES_PATH}/<id> answers GET only`,
355
+ });
356
+ const authorization = request.headers.get("authorization") ?? undefined;
357
+ const authn = authenticateIngressBearer(authorization, env.SWITCHBOARD_INGRESS_TOKENS, "coordinator");
358
+ if (!authn.ok) {
359
+ console.warn(`[coordinator] instance status ${authn.status} — ${authn.reason}`);
360
+ return json(authn.status, { ok: false, error: authn.reason });
361
+ }
362
+ let answer: Response;
363
+ try {
364
+ answer = await getContainer(env.SWITCHBOARD, INSTANCE).fetch(
365
+ new Request(`${INTERNAL}${COORDINATOR_AUTHORIZE_PATH}`, {
366
+ method: "POST",
367
+ headers: { authorization: authorization ?? "" },
368
+ }),
369
+ );
370
+ } catch (err) {
371
+ const reason = err instanceof Error ? err.message : String(err);
372
+ return json(503, { ok: false, error: `coordinator disabled: the bot could not be asked (${reason})` });
373
+ }
374
+ const auth = parseSubjectAuthorization(answer.status, await answer.text().catch(() => ""));
375
+ if (!auth.ok) return json(auth.status, { ok: false, error: auth.reason });
376
+ let outcome: Parameters<typeof instanceStatusResponse>[0];
377
+ try {
378
+ const status = await (await env.SHIP_COORDINATOR.get(id)).status();
379
+ outcome = { kind: "status", id, status: status.status };
380
+ } catch (err) {
381
+ // Absence is only the engine's own word for it — the `instance.not_found`
382
+ // code the Workflows binding throws for an id it has never seen. Any other
383
+ // failure, whatever its text says, is a failure by reason: a wrong absence
384
+ // would let a re-issue write over a live runner's records.
385
+ const reason = err instanceof Error ? err.message : String(err);
386
+ outcome = isInstanceNotFound(reason) ? { kind: "absent", id } : { kind: "failed", id, reason };
387
+ }
388
+ const res = instanceStatusResponse(outcome);
389
+ return json(res.status, res.body);
390
+ }
391
+
263
392
  /** Record a firing on the state Worker's ScheduleDO (the /runs Scheduled panel
264
393
  * reads it). Best-effort: a failure here is a log line — the run itself (if
265
394
  * any) already happened and is its own record. */
@@ -283,11 +412,18 @@ export default {
283
412
  const root = tracer.start("bot-shim.fetch", { sinks: traceSinks, attrs: { route } });
284
413
  try {
285
414
  const forwarded = withTraceContext(inbound, root);
286
- // The one route the Worker answers itself; everything else is the container's.
415
+ // The routes the Worker answers itself — the restart, the coordinator's
416
+ // instance creation and an instance's status, all over bindings only this
417
+ // Worker holds; everything else is the container's.
418
+ const statusId = parseInstanceStatusPath(pathname);
287
419
  const res =
288
420
  pathname === "/admin/restart"
289
421
  ? await handleAdminRestart(forwarded, env)
290
- : await getContainer(env.SWITCHBOARD, INSTANCE).fetch(forwarded);
422
+ : pathname === COORDINATOR_INSTANCES_PATH
423
+ ? await handleCoordinatorInstances(forwarded, env)
424
+ : statusId !== undefined
425
+ ? await handleCoordinatorInstanceStatus(forwarded, env, statusId)
426
+ : await getContainer(env.SWITCHBOARD, INSTANCE).fetch(forwarded);
291
427
  root.end(res.status >= 500 ? "error" : "ok", { httpStatus: res.status });
292
428
  return res;
293
429
  } catch (err) {
@@ -55,6 +55,15 @@
55
55
  "bindings": [{ "name": "SWITCHBOARD", "class_name": "SwitchboardServer" }]
56
56
  },
57
57
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SwitchboardServer"] }],
58
+ // The ship coordinator as a Workflow (coordinator.ts, re-exported by
59
+ // worker.ts; docs/reference/specs/http-ingress.md item 9): `POST
60
+ // /admin/coordinator/instances` creates its instances and the state Worker's
61
+ // finish sends them `run finished:<runId>` through its own cross-script
62
+ // binding to this class (deploy/cloudflare-memory/wrangler.template.jsonc).
63
+ // Same-script here, so this Worker deploys on its own.
64
+ "workflows": [
65
+ { "name": "{{script}}-ship-coordinator", "binding": "SHIP_COORDINATOR", "class_name": "ShipCoordinator" }
66
+ ],
58
67
  // Every cron here IS a schedule in src/core/schedules.ts (the registry; a unit
59
68
  // test fails when the two sets differ) and worker.ts looks each firing up there.
60
69
  // "* * * * *": keep-alive (/healthz) — also what RESTARTS the container after a