0sec-cli 0.24.1 → 0.25.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 (243) hide show
  1. package/0.js +64 -60
  2. package/README.md +121 -82
  3. package/chunks/{adapt-loop-L77BWMNE.js → adapt-loop-L2PXCD4J.js} +6 -5
  4. package/chunks/{artifact-scraper-BV67RAP5.js → artifact-scraper-W3LGLXBM.js} +4 -3
  5. package/chunks/{assumption-mining-RPVR77A3.js → assumption-mining-7IJ6ZWZE.js} +22 -20
  6. package/chunks/{backend-connections-ACIXNYZN.js → backend-connections-MYH4QRQG.js} +5 -4
  7. package/chunks/{chunk-QNRYOJEB.js → chunk-27SXCYWH.js} +1 -1
  8. package/chunks/{chunk-PJU24AKR.js → chunk-2J3STRJT.js} +337 -257
  9. package/chunks/chunk-36J5ZLFE.js +18729 -0
  10. package/chunks/chunk-3DF55BWV.js +49 -0
  11. package/chunks/{chunk-F2P25DJY.js → chunk-3HMO42M2.js} +1417 -316
  12. package/chunks/{chunk-PVJUPIQG.js → chunk-3T5R7P4T.js} +6 -6
  13. package/chunks/{chunk-SU4IHQQI.js → chunk-5H2PXZ7Q.js} +1 -1
  14. package/chunks/chunk-5XOYTAOK.js +1546 -0
  15. package/chunks/{chunk-X2IPO5JO.js → chunk-6TA3CWND.js} +2656 -2960
  16. package/chunks/{chunk-TVS6WVSK.js → chunk-76CLUI54.js} +3 -3
  17. package/chunks/{chunk-KCA35IL3.js → chunk-7PUJI64A.js} +11 -9
  18. package/chunks/{chunk-2NBZZFRG.js → chunk-7TIWL6E5.js} +8 -10
  19. package/chunks/chunk-ABKUI3QB.js +196 -0
  20. package/chunks/{chunk-3LPBJMHB.js → chunk-ACYZMJHC.js} +1 -1
  21. package/chunks/{chunk-H5QURBBK.js → chunk-AL32F74R.js} +1 -1
  22. package/chunks/{chunk-RURJYCWY.js → chunk-B5TIXHCO.js} +1 -1
  23. package/chunks/{chunk-GZA7RGK7.js → chunk-BWUW6ZQR.js} +2052 -1075
  24. package/chunks/{chunk-I43YYHDU.js → chunk-C6ZMPXN2.js} +5 -50
  25. package/chunks/{chunk-ILRMLA6J.js → chunk-D4IBEO2K.js} +1 -1
  26. package/chunks/{chunk-K74DRDC5.js → chunk-DHNYJ2ML.js} +1 -1
  27. package/chunks/{chunk-YKWARO3T.js → chunk-DJGJECBV.js} +93 -20
  28. package/chunks/{chunk-RVJVM5L4.js → chunk-DMDWIW3E.js} +5 -5
  29. package/chunks/{chunk-ZM6ANQZJ.js → chunk-EEP6GBUO.js} +16037 -34417
  30. package/chunks/{chunk-IJ6UPW3H.js → chunk-EIONVEFN.js} +208 -29
  31. package/chunks/chunk-EQBC2ZWL.js +2481 -0
  32. package/chunks/{chunk-3WUPCZGF.js → chunk-FI2H6HV2.js} +1 -1
  33. package/chunks/{chunk-5NFIY2SJ.js → chunk-FI5YL4R2.js} +3 -3
  34. package/chunks/{chunk-XA2IBHPB.js → chunk-FOLTHXRH.js} +1 -1
  35. package/chunks/{chunk-CJRZSWII.js → chunk-GOUGG7IQ.js} +3 -3
  36. package/chunks/{chunk-NBV4I237.js → chunk-GXSECFUE.js} +1 -1
  37. package/chunks/{chunk-BD7WULFS.js → chunk-HDJSHLRM.js} +3 -2
  38. package/chunks/{chunk-5WAHHZBF.js → chunk-HFUOUT2B.js} +61232 -30665
  39. package/chunks/chunk-HJKJ4NQP.js +196 -0
  40. package/chunks/{chunk-UJE4WOQS.js → chunk-I4VIAQHM.js} +1 -1
  41. package/chunks/{chunk-5ETI27Q3.js → chunk-ITEMDJAB.js} +3 -3
  42. package/chunks/chunk-ITHDAIRY.js +57 -0
  43. package/chunks/{chunk-R2XBH476.js → chunk-JCRQV4FE.js} +2 -2
  44. package/chunks/{chunk-SQCBHFWN.js → chunk-LWMEJYCP.js} +2 -2
  45. package/chunks/{chunk-5PFRE522.js → chunk-MY44EZKD.js} +1 -1
  46. package/chunks/{chunk-7UHAXEOV.js → chunk-NBLKIQCW.js} +226 -4
  47. package/chunks/chunk-NO543ZBU.js +90 -0
  48. package/chunks/{chunk-X2ZMSRTP.js → chunk-OFPF26WL.js} +333 -3
  49. package/chunks/{chunk-YERUDEXT.js → chunk-PAE7ZAV6.js} +2780 -1255
  50. package/chunks/{chunk-S2GITRD5.js → chunk-PMAKX6KP.js} +2 -2
  51. package/chunks/{chunk-LMP2T4G2.js → chunk-RBTJYREN.js} +8 -171
  52. package/chunks/{chunk-CGBDEST2.js → chunk-RFK5F45W.js} +1 -1
  53. package/chunks/{chunk-GTED3FSZ.js → chunk-S2WCYEK2.js} +4 -99
  54. package/chunks/{chunk-GXXX7AGH.js → chunk-S3B6HFSM.js} +144 -14
  55. package/chunks/{chunk-WUPN3D37.js → chunk-S4DFH2FJ.js} +26 -1
  56. package/chunks/{chunk-Q7JKIKKF.js → chunk-SEWZ76MN.js} +77 -13
  57. package/chunks/{chunk-6XMFCVJS.js → chunk-TFKY6D7O.js} +1 -1
  58. package/chunks/{chunk-BOBIAHKA.js → chunk-WK6VUCKK.js} +3 -333
  59. package/chunks/{chunk-IXT64DV5.js → chunk-XPDJE537.js} +97 -1
  60. package/chunks/{chunk-BMDG6T7D.js → chunk-YXTV2FRE.js} +113 -22
  61. package/chunks/{chunk-SZDD77KX.js → chunk-YYFODVZG.js} +6 -10
  62. package/chunks/{chunk-JWSOUKFF.js → chunk-ZWAWZU6C.js} +30 -13
  63. package/chunks/{chunk-GV5WXDHY.js → chunk-ZZV56ACK.js} +11 -9
  64. package/chunks/{codex-models-6XVNFRXZ.js → codex-models-KCUIVS3R.js} +9 -8
  65. package/chunks/{commands-FRPOFIO4.js → commands-MPPAADGY.js} +798 -608
  66. package/chunks/dashboard-MN7GZLLX.js +75 -0
  67. package/chunks/{db-BXJNWUYJ.js → db-R2AQSESC.js} +5 -4
  68. package/chunks/{deep-review-GSJEBLCG.js → deep-review-Q6BJKEV4.js} +37 -38
  69. package/chunks/{disclose-OU6Z6GVT.js → disclose-GGULOW27.js} +6 -5
  70. package/chunks/{dist-TQMHMSBZ.js → dist-6RNAVFAY.js} +376 -333
  71. package/chunks/{dist-7KSYDDSG.js → dist-DWN7U5OV.js} +5 -2
  72. package/chunks/{dist-M2X367VW.js → dist-JAZWL3SC.js} +3 -2
  73. package/chunks/{dist-RKLQIXDT.js → dist-OLGNKDB2.js} +4 -3
  74. package/chunks/{dist-LNXGATR2.js → dist-WNAOCQGA.js} +5 -4
  75. package/chunks/eval-runner-WCTTIYN4.js +38 -0
  76. package/chunks/{exploit-agent-Y2P4GFIY.js → exploit-agent-M3KUMZJH.js} +1 -1
  77. package/chunks/{exploit-autoclimb-YRZ3TMS2.js → exploit-autoclimb-QTTWC5WM.js} +1 -1
  78. package/chunks/{exploit-climb-4LEY4SDW.js → exploit-climb-E4KDELTN.js} +4 -3
  79. package/chunks/{fix-RQSIG75B.js → fix-U5FFJD4F.js} +9 -8
  80. package/chunks/{harness-3NVC3DDY.js → harness-FMKIMEH5.js} +5 -4
  81. package/chunks/{http-conformance-U5F6GKYG.js → http-conformance-AXMUOE6C.js} +3 -2
  82. package/chunks/hunt-scan-RYPWRUQD.js +43 -0
  83. package/chunks/{kernel-primitive-T47V3RD3.js → kernel-primitive-CVG2E7MJ.js} +4 -3
  84. package/chunks/{kernel-vm-runner-AEL5E3SV.js → kernel-vm-runner-62KISULQ.js} +4 -3
  85. package/chunks/{local-engine-ZCW4YC6E.js → local-engine-WP3I7K7G.js} +7 -6
  86. package/chunks/managed-operation-642WPTLC.js +59 -0
  87. package/chunks/{memsafety-scan-L2Z7E7TE.js → memsafety-scan-Q2MBZEGJ.js} +1 -2
  88. package/chunks/{native-loop-26X2CHBV.js → native-loop-HC77R5NQ.js} +31 -16
  89. package/chunks/operator-services-E2NONBQS.js +79 -0
  90. package/chunks/orchestrate-Z3DW265M.js +62 -0
  91. package/chunks/{prepare-C3KGMBXT.js → prepare-7DTS6RJT.js} +7 -6
  92. package/chunks/{remote-workflow-runtime-DB52SINR.js → remote-workflow-runtime-6CEVRZXS.js} +4 -3
  93. package/chunks/routing-ITJZBJIU.js +61 -0
  94. package/chunks/{runtime-U66JNSAY.js → runtime-PZJ77QES.js} +9 -8
  95. package/chunks/{runtime-AUBLQUOH.js → runtime-W6JNDQRO.js} +4 -3
  96. package/chunks/{scan-stream-34F3LTPV.js → scan-stream-6VOJTHTR.js} +6 -4
  97. package/chunks/{scope-HSTG63TQ.js → scope-FHXW6EX6.js} +4 -3
  98. package/chunks/service-plugins-CN6Q2NSN.js +63 -0
  99. package/chunks/{session-store-MKAAP3WL.js → session-store-DBBI544Y.js} +8 -5
  100. package/chunks/{variant-candidates-LG767HWH.js → variant-candidates-DPR6DJ3N.js} +9 -8
  101. package/chunks/{web-recon-prepass-NJP6J3E4.js → web-recon-prepass-HWDHDQMN.js} +7 -6
  102. package/chunks/{workflow-runtime-AR627I26.js → workflow-runtime-PLB32XC3.js} +50 -49
  103. package/dashboard/assets/{abnfDiagram-VCTEODGH-Cq149Jpk.js → abnfDiagram-VCTEODGH-6e39CQKz.js} +1 -1
  104. package/dashboard/assets/{arc-CpP-3TRW.js → arc-ClYun86B.js} +1 -1
  105. package/dashboard/assets/architecture-7GRP2DOG-rM-lWo7E.js +1 -0
  106. package/dashboard/assets/architectureDiagram-5GKGNRK7-C9gEGZIU.js +36 -0
  107. package/dashboard/assets/{blockDiagram-I7D4REHJ-CZYkRRDi.js → blockDiagram-I7D4REHJ-Dgh2n5Lf.js} +2 -2
  108. package/dashboard/assets/{c4Diagram-7LVT6UL2-CV7Iu3_Z.js → c4Diagram-7LVT6UL2-CsorW08z.js} +1 -1
  109. package/dashboard/assets/channel-BBlDqIBl.js +1 -0
  110. package/dashboard/assets/{chunk-4HAMMTFA-PKCyW4m-.js → chunk-4HAMMTFA-4_b_LGGQ.js} +1 -1
  111. package/dashboard/assets/{chunk-75Z2AOVW-C5MlFTc_.js → chunk-75Z2AOVW-qzNlKGCp.js} +1 -1
  112. package/dashboard/assets/chunk-DU6HZSFF-SAPICW28.js +127 -0
  113. package/dashboard/assets/{chunk-F27PBJKO-DTAok0qM.js → chunk-F27PBJKO-DlLotIQx.js} +1 -1
  114. package/dashboard/assets/{chunk-FOHPRMQF-DHwB1DNv.js → chunk-FOHPRMQF-COOliuUh.js} +14 -14
  115. package/dashboard/assets/{chunk-GMAD6QVW-CPsr2F33.js → chunk-GMAD6QVW-DNnr65Nb.js} +7 -7
  116. package/dashboard/assets/{chunk-GVQU2GXP-C2AXGf9A.js → chunk-GVQU2GXP-DTwv0VfU.js} +1 -1
  117. package/dashboard/assets/{chunk-IMKFNOWR-DMG6yD_O.js → chunk-IMKFNOWR-B1fk1q_W.js} +1 -1
  118. package/dashboard/assets/{chunk-L3NEJ4N5-BSvwdbXx.js → chunk-L3NEJ4N5-ByVjFIox.js} +1 -1
  119. package/dashboard/assets/{chunk-OSK3NFVY-CfB8V7FU.js → chunk-OSK3NFVY-DUDCzjbh.js} +1 -1
  120. package/dashboard/assets/{chunk-P2QGCYS3-CvmZvq6n.js → chunk-P2QGCYS3-D_D5qEso.js} +1 -1
  121. package/dashboard/assets/{chunk-POPQ4Y6H-D_TvwDxL.js → chunk-POPQ4Y6H-CetM-tVm.js} +1 -1
  122. package/dashboard/assets/{chunk-PWAF6VOD-Da5GnuOY.js → chunk-PWAF6VOD-BcJX4exl.js} +1 -1
  123. package/dashboard/assets/{chunk-SHT3W25Y-CcsMJpR-.js → chunk-SHT3W25Y-BWQql238.js} +1 -1
  124. package/dashboard/assets/{chunk-SVP7TREG-COojjieJ.js → chunk-SVP7TREG-BSLOD1JE.js} +1 -1
  125. package/dashboard/assets/{chunk-TICWLB2K-nBAmJwx6.js → chunk-TICWLB2K-DReOl-g_.js} +2 -2
  126. package/dashboard/assets/{chunk-XXDRQBXY-r5HyhWDv.js → chunk-XXDRQBXY-DSdSMeei.js} +1 -1
  127. package/dashboard/assets/classDiagram-ZZMXUADV-CRH2kB8G.js +1 -0
  128. package/dashboard/assets/classDiagram-v2-VYDZK3BY-CRH2kB8G.js +1 -0
  129. package/dashboard/assets/common-CRRsylCH.js +5 -0
  130. package/dashboard/assets/cose-bilkent-JH36ORCC-fg-DIBqt.js +1 -0
  131. package/dashboard/assets/cynefin-OW5HDTMX-XmrbEIFh.js +1 -0
  132. package/dashboard/assets/{cynefinDiagram-5FMLGOSQ-xodkuQtW.js → cynefinDiagram-5FMLGOSQ-CoQ7QEPl.js} +1 -1
  133. package/dashboard/assets/cytoscape.esm-BbBREfsg.js +321 -0
  134. package/dashboard/assets/dagre-D7JM2Yhv.js +1 -0
  135. package/dashboard/assets/dagre-GXQ25YYZ-qtQtsg6c.js +4 -0
  136. package/dashboard/assets/{diagram-S7CK7UJ4-DNoTMlCO.js → diagram-S7CK7UJ4-B_ts4_58.js} +2 -2
  137. package/dashboard/assets/{diagram-UQ7AKVKN-COccuyew.js → diagram-UQ7AKVKN-DwHbbxFy.js} +1 -1
  138. package/dashboard/assets/{diagram-VSXAHHWV-g6D1tWRb.js → diagram-VSXAHHWV-2Tg_RG89.js} +1 -1
  139. package/dashboard/assets/{diagram-VX7I27RA-DhO4eVIC.js → diagram-VX7I27RA-BFYp-YjF.js} +1 -1
  140. package/dashboard/assets/{diagram-Z3DM3KII-DeOQFexd.js → diagram-Z3DM3KII-DrPiS4V0.js} +1 -1
  141. package/dashboard/assets/{dist-BQYeOQg3.js → dist-cbVssBvx.js} +1 -1
  142. package/dashboard/assets/{ebnfDiagram-PWID7BFC-MtkX2feM.js → ebnfDiagram-PWID7BFC-DCMOZtU2.js} +1 -1
  143. package/dashboard/assets/{erDiagram-RLTQ6QDP-DIWSsoYZ.js → erDiagram-RLTQ6QDP-CW4rUJGU.js} +1 -1
  144. package/dashboard/assets/eventmodeling-NTZA5JFV-C2q-Aaav.js +1 -0
  145. package/dashboard/assets/flowDiagram-HODETNUW-3NOabgG3.js +1 -0
  146. package/dashboard/assets/{ganttDiagram-EL5Y4UJY-DKggkJe0.js → ganttDiagram-EL5Y4UJY-C1AqcwIF.js} +1 -1
  147. package/dashboard/assets/gitGraph-4MIJSDKK-DYR76gtd.js +1 -0
  148. package/dashboard/assets/{gitGraphDiagram-WWUBYQGX-r_iR8Qzo.js → gitGraphDiagram-WWUBYQGX-B7hL98CE.js} +1 -1
  149. package/dashboard/assets/info-A6RAGUB7-TXB43LiH.js +1 -0
  150. package/dashboard/assets/{infoDiagram-27XIBGKW-ClQk7UyQ.js → infoDiagram-27XIBGKW-Dk5dXmh_.js} +1 -1
  151. package/dashboard/assets/{ishikawaDiagram-5VMMS53U-tR7Vziog.js → ishikawaDiagram-5VMMS53U-BQDuNMka.js} +2 -2
  152. package/dashboard/assets/{journeyDiagram-3NMN7TZE-Cpb9IqKL.js → journeyDiagram-3NMN7TZE-DkustddD.js} +1 -1
  153. package/dashboard/assets/{kanban-definition-UXKFOSKX-BzUuKbA9.js → kanban-definition-UXKFOSKX-BW7Ann84.js} +1 -1
  154. package/dashboard/assets/{katex-BdBmVSus.js → katex-nLxf_RlZ.js} +2 -2
  155. package/dashboard/assets/{line-BHY_tSHu.js → line-DVxcLeru.js} +1 -1
  156. package/dashboard/assets/linear-DOM_l9bO.js +1 -0
  157. package/dashboard/assets/mermaid-parser.core-CKg9fDvJ.js +7 -0
  158. package/dashboard/assets/{mermaid.core-D_QsUPU8.js → mermaid.core-DruW_5-Y.js} +9 -9
  159. package/dashboard/assets/{mindmap-definition-YA3MSWOX-CtfLTPPJ.js → mindmap-definition-YA3MSWOX-Di5W5VB3.js} +1 -1
  160. package/dashboard/assets/operations-B-t_k60U.js +194 -0
  161. package/dashboard/assets/operations-D2ZKZFOF.css +1 -0
  162. package/dashboard/assets/packet-AYTQ26CC-BABwqtNu.js +1 -0
  163. package/dashboard/assets/{pegDiagram-XKGWAZYB-C1Dclbzj.js → pegDiagram-XKGWAZYB-Cx4ABT6q.js} +1 -1
  164. package/dashboard/assets/pie-WAS4IAKB-CNdXyrze.js +1 -0
  165. package/dashboard/assets/{pieDiagram-E7YTZNPT-Cd7XwYo5.js → pieDiagram-E7YTZNPT-BilfQbIs.js} +1 -1
  166. package/dashboard/assets/{quadrantDiagram-AXDQQJYC-CaS-GQ4X.js → quadrantDiagram-AXDQQJYC-BVmMQHE5.js} +1 -1
  167. package/dashboard/assets/radar-RG4KPBEZ-fKNmDeoK.js +1 -0
  168. package/dashboard/assets/railroad-74A4TZTK-DhcvDCOh.js +1 -0
  169. package/dashboard/assets/railroad-abnf-HS5TGJTU-BTRVAmf0.js +1 -0
  170. package/dashboard/assets/railroad-ebnf-LZEXJU2U-CwS8fgC_.js +1 -0
  171. package/dashboard/assets/railroad-peg-WCYAUIDC-CXuhXlNp.js +1 -0
  172. package/dashboard/assets/{railroadDiagram-O6MQD6OU-BeesTrqn.js → railroadDiagram-O6MQD6OU-DnBru-jf.js} +1 -1
  173. package/dashboard/assets/{requirementDiagram-BXWQKSXE-IjyK0DhW.js → requirementDiagram-BXWQKSXE-FwdvUZ7_.js} +1 -1
  174. package/dashboard/assets/{sankeyDiagram-P5KCCOFB-DmIluQqo.js → sankeyDiagram-P5KCCOFB-BQvDORai.js} +1 -1
  175. package/dashboard/assets/{sequenceDiagram-WJ2MYXX4-CUp8ZjTc.js → sequenceDiagram-WJ2MYXX4-FWYQLzcY.js} +2 -2
  176. package/dashboard/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  177. package/dashboard/assets/src-BwkPhlcp.js +1 -0
  178. package/dashboard/assets/{stateDiagram-D77RDMKH-Dy9TQb8P.js → stateDiagram-D77RDMKH-CjPBd_Sa.js} +1 -1
  179. package/dashboard/assets/stateDiagram-v2-MP3YSRHH-DHmenujm.js +1 -0
  180. package/dashboard/assets/swimlanes-42K2YHIH-SQLNsa4J.js +1 -0
  181. package/dashboard/assets/swimlanesDiagram-VR7AAH4N-D1bH4ETZ.js +8 -0
  182. package/dashboard/assets/{timeline-definition-24CTP7MA-BGdXW-iY.js → timeline-definition-24CTP7MA-BxCf20k1.js} +1 -1
  183. package/dashboard/assets/treeView-Q6P3EWNA-BGLYAyGp.js +1 -0
  184. package/dashboard/assets/treemap-WGGIJYW6-BlzAwncU.js +1 -0
  185. package/dashboard/assets/{vennDiagram-4TSXK5OY-DzKWHJsl.js → vennDiagram-4TSXK5OY-D76RIZLz.js} +1 -1
  186. package/dashboard/assets/wardley-WFR3VGLG-Ba_reiVw.js +1 -0
  187. package/dashboard/assets/{wardleyDiagram-VM6X3IG4-DGsjYmNJ.js → wardleyDiagram-VM6X3IG4-Cux4coOD.js} +1 -1
  188. package/dashboard/assets/{xychartDiagram-S5SC5T6Z-C7_T_tWt.js → xychartDiagram-S5SC5T6Z-Ch5mvnlH.js} +1 -1
  189. package/dashboard/index.html +2 -2
  190. package/package.json +2 -5
  191. package/chunks/chunk-2XGBEH53.js +0 -4304
  192. package/chunks/chunk-4ROZFLSR.js +0 -279
  193. package/chunks/chunk-56UTQKXO.js +0 -56
  194. package/chunks/chunk-BPU6M42R.js +0 -77
  195. package/chunks/chunk-IIBNQTOK.js +0 -1532
  196. package/chunks/chunk-NJLKFC66.js +0 -4790
  197. package/chunks/chunk-OOMBOLAF.js +0 -295
  198. package/chunks/chunk-VIT5ALXF.js +0 -4054
  199. package/chunks/chunk-VU7V2SDW.js +0 -284
  200. package/chunks/chunk-W624J7NF.js +0 -117
  201. package/chunks/chunk-YSQWZ67M.js +0 -6027
  202. package/chunks/dashboard-5HDSNBAU.js +0 -76
  203. package/chunks/eval-runner-A2DIGVP7.js +0 -31
  204. package/chunks/hunt-scan-W246VWJC.js +0 -40
  205. package/chunks/managed-operation-6R2QPPW5.js +0 -60
  206. package/chunks/orchestrate-OBS37FWQ.js +0 -63
  207. package/chunks/run-MU3MFL2Z.js +0 -28242
  208. package/chunks/service-plugins-5B23PWXC.js +0 -64
  209. package/dashboard/assets/architecture-7GRP2DOG-z5EHYkyH.js +0 -1
  210. package/dashboard/assets/architectureDiagram-5GKGNRK7-ewtXpjR_.js +0 -36
  211. package/dashboard/assets/channel-CuYjfiC5.js +0 -1
  212. package/dashboard/assets/chunk-DU6HZSFF-3M37ulcO.js +0 -127
  213. package/dashboard/assets/classDiagram-ZZMXUADV-DCuOrWUV.js +0 -1
  214. package/dashboard/assets/classDiagram-v2-VYDZK3BY-DCuOrWUV.js +0 -1
  215. package/dashboard/assets/common-C6snVPsk.js +0 -5
  216. package/dashboard/assets/cose-bilkent-JH36ORCC-BLGcaZHk.js +0 -1
  217. package/dashboard/assets/cynefin-OW5HDTMX-DbZMK00I.js +0 -1
  218. package/dashboard/assets/cytoscape.esm-CGd-uY3x.js +0 -321
  219. package/dashboard/assets/dagre-GXQ25YYZ-CRk9qfSG.js +0 -4
  220. package/dashboard/assets/dagre-PrKaheQc.js +0 -1
  221. package/dashboard/assets/eventmodeling-NTZA5JFV-D_3ez4Gv.js +0 -1
  222. package/dashboard/assets/flowDiagram-HODETNUW-D2boyYEd.js +0 -1
  223. package/dashboard/assets/gitGraph-4MIJSDKK-DFOQYwXc.js +0 -1
  224. package/dashboard/assets/info-A6RAGUB7-DeednLoB.js +0 -1
  225. package/dashboard/assets/linear-naZZ1pHk.js +0 -1
  226. package/dashboard/assets/mermaid-parser.core-CPvd4cEx.js +0 -7
  227. package/dashboard/assets/operations-BwCzhVnT.js +0 -130
  228. package/dashboard/assets/operations-QjKT0Hwi.css +0 -1
  229. package/dashboard/assets/packet-AYTQ26CC-DDQbkT9I.js +0 -1
  230. package/dashboard/assets/pie-WAS4IAKB-LpU4ULAs.js +0 -1
  231. package/dashboard/assets/radar-RG4KPBEZ-CcxW3NJm.js +0 -1
  232. package/dashboard/assets/railroad-74A4TZTK-9P5Rl8fP.js +0 -1
  233. package/dashboard/assets/railroad-abnf-HS5TGJTU-CSfpwjna.js +0 -1
  234. package/dashboard/assets/railroad-ebnf-LZEXJU2U-D6R5qzr-.js +0 -1
  235. package/dashboard/assets/railroad-peg-WCYAUIDC-DKEX7ITG.js +0 -1
  236. package/dashboard/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
  237. package/dashboard/assets/src-r61wDjcA.js +0 -1
  238. package/dashboard/assets/stateDiagram-v2-MP3YSRHH-CQb0ZtPr.js +0 -1
  239. package/dashboard/assets/swimlanes-42K2YHIH-CZuDV7V5.js +0 -1
  240. package/dashboard/assets/swimlanesDiagram-VR7AAH4N-BxgWMq8X.js +0 -8
  241. package/dashboard/assets/treeView-Q6P3EWNA-DlRrayjM.js +0 -1
  242. package/dashboard/assets/treemap-WGGIJYW6-C6UqLKfI.js +0 -1
  243. package/dashboard/assets/wardley-WFR3VGLG-thmyHKb0.js +0 -1
@@ -4,24 +4,21 @@ const require = __0CreateRequire(import.meta.url);
4
4
  import {
5
5
  ConsoleGateway,
6
6
  ConsoleGatewayError,
7
- WebOperatorServices,
8
7
  WebWorkflowService,
9
- WorkflowEngineService
10
- } from "./chunk-GZA7RGK7.js";
11
- import {
12
- assertLocalEngineAvailable,
13
- registerLocalEngine
14
- } from "./chunk-RVJVM5L4.js";
15
- import {
16
- findingFromRow as findingFromRow2
17
- } from "./chunk-W624J7NF.js";
18
- import {
8
+ WorkflowEngineService,
19
9
  buildFindingConsoleCommand,
20
10
  createLocalConsoleSession,
21
- createSessionPluginHostManager,
22
11
  findingFromRow,
23
12
  loadFindingFocus
24
- } from "./chunk-2XGBEH53.js";
13
+ } from "./chunk-BWUW6ZQR.js";
14
+ import {
15
+ assertLocalEngineAvailable,
16
+ registerLocalEngine
17
+ } from "./chunk-DMDWIW3E.js";
18
+ import {
19
+ WebOperatorServices,
20
+ createSessionPluginHostManager
21
+ } from "./chunk-EQBC2ZWL.js";
25
22
  import {
26
23
  BackendConnectionRegistry,
27
24
  backendBearerFromEnv,
@@ -29,13 +26,11 @@ import {
29
26
  handleBackendConnectionRequest,
30
27
  loadManagedWorker,
31
28
  validateWorkerOptions
32
- } from "./chunk-Q7JKIKKF.js";
33
- import {
34
- Option
35
- } from "./chunk-WUPN3D37.js";
29
+ } from "./chunk-SEWZ76MN.js";
36
30
  import {
31
+ Option,
37
32
  presentationEventBus
38
- } from "./chunk-56UTQKXO.js";
33
+ } from "./chunk-S4DFH2FJ.js";
39
34
  import {
40
35
  source_default
41
36
  } from "./chunk-HLFS6WV2.js";
@@ -61,43 +56,47 @@ import {
61
56
  validateWorkbenchSourceLesson,
62
57
  workbenchConfigPath,
63
58
  workbenchNetworkEnabled
64
- } from "./chunk-X2IPO5JO.js";
59
+ } from "./chunk-6TA3CWND.js";
65
60
  import {
66
61
  connectServicePlugins,
67
62
  createConsoleRuntime,
68
63
  exportReport,
69
64
  isReportExportFormat
70
- } from "./chunk-ZM6ANQZJ.js";
65
+ } from "./chunk-EEP6GBUO.js";
71
66
  import {
72
67
  HuntMemoryStore,
73
68
  TOOL_DEFINITIONS,
74
69
  discoverAgentSkills,
70
+ listBrowserViews,
75
71
  loadSkillRegistry,
72
+ parseImpactAssessment,
76
73
  readToolCallNames,
74
+ subscribeBrowserView,
77
75
  validateAgentSkillContent
78
- } from "./chunk-5WAHHZBF.js";
76
+ } from "./chunk-HFUOUT2B.js";
79
77
  import {
80
78
  SecurityWorkflowStore,
81
79
  WorkflowTriggerStore,
82
80
  findingStatuses,
83
81
  nextWorkflowTriggerFire,
84
82
  osecDB,
85
- resolveOsecDbPath
86
- } from "./chunk-BMDG6T7D.js";
83
+ resolveOsecDbPath,
84
+ restoreFindingReviewFields
85
+ } from "./chunk-YXTV2FRE.js";
87
86
  import {
88
87
  LlmApiRuntime
89
- } from "./chunk-IJ6UPW3H.js";
88
+ } from "./chunk-EIONVEFN.js";
90
89
  import {
91
90
  DEFAULT_WORKBENCH_BROKER_LIMITS,
92
91
  approveSmolvmWorkbenchImage,
93
92
  getSmolvmWorkbenchStatus,
94
93
  isAdmittedSmolvmWorkbench,
95
94
  resolveSmolvmRuntime
96
- } from "./chunk-PVJUPIQG.js";
95
+ } from "./chunk-3T5R7P4T.js";
97
96
  import {
98
97
  ScopePolicy,
99
98
  withScopeEnforcement
100
- } from "./chunk-QNRYOJEB.js";
99
+ } from "./chunk-27SXCYWH.js";
101
100
  import {
102
101
  BackendDescriptorSchema,
103
102
  BackendRequestIdSchema,
@@ -106,14 +105,14 @@ import {
106
105
  VERSION,
107
106
  createPresentationEvent,
108
107
  homeStateDir
109
- } from "./chunk-GXXX7AGH.js";
108
+ } from "./chunk-S3B6HFSM.js";
110
109
  import {
111
110
  external_exports
112
- } from "./chunk-VIT5ALXF.js";
111
+ } from "./chunk-5XOYTAOK.js";
113
112
 
114
113
  // packages/cli/src/commands/dashboard.ts
115
114
  import { createServer as createServer2 } from "node:http";
116
- import { createHash as createHash8, randomUUID as randomUUID7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
115
+ import { createHash as createHash8, randomUUID as randomUUID9, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
117
116
  import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
118
117
  import { existsSync as existsSync2, mkdirSync as mkdirSync6, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
119
118
  import { dirname as dirname2, extname, join as join7, resolve as resolve4, sep } from "node:path";
@@ -132,6 +131,44 @@ import { createServer } from "node:http";
132
131
  import { randomUUID } from "node:crypto";
133
132
  import { spawn } from "node:child_process";
134
133
  import { mkdir, writeFile } from "node:fs/promises";
134
+
135
+ // packages/cli/src/tui/findings-data.ts
136
+ function parseJsonColumn(raw) {
137
+ if (!raw) return void 0;
138
+ try {
139
+ return JSON.parse(raw);
140
+ } catch {
141
+ return void 0;
142
+ }
143
+ }
144
+ function findingFromRow2(row) {
145
+ const impactAssessment = parseJsonColumn(row.impactAssessment) === void 0 ? void 0 : parseImpactAssessment(row.impactAssessment) ?? void 0;
146
+ const record = {
147
+ id: row.id,
148
+ templateId: row.templateId,
149
+ title: row.title,
150
+ description: row.description,
151
+ severity: row.severity,
152
+ cvssScore: row.cvssScore ?? void 0,
153
+ cvssVector: row.cvssVector ?? void 0,
154
+ category: row.category,
155
+ status: row.status,
156
+ fingerprint: row.fingerprint ?? void 0,
157
+ triageStatus: row.triageStatus ?? void 0,
158
+ timestamp: row.timestamp,
159
+ evidence: {
160
+ request: row.evidenceRequest,
161
+ response: row.evidenceResponse,
162
+ analysis: row.evidenceAnalysis ?? void 0
163
+ },
164
+ verificationSpec: parseJsonColumn(row.verificationSpec),
165
+ impactAssessment,
166
+ ...restoreFindingReviewFields(row)
167
+ };
168
+ return record;
169
+ }
170
+
171
+ // packages/cli/src/workbench-console-guest.ts
135
172
  var CALLBACKS = ["requestScope", "requestLocalScope", "approveTool", "escalateScopedAudit", "askOperator"];
136
173
  var EVENTS = ["onHarnessUpdate", "onAssistantDelta", "onReasoningDelta", "onToolStart", "onToolResult", "onUsage", "onNotice", "onCompaction"];
137
174
  async function runGuest(cli) {
@@ -156,9 +193,9 @@ async function runGuest(cli) {
156
193
  let policy;
157
194
  const callback = (name, args) => {
158
195
  if (decisions.size >= WORKBENCH_MAX_PENDING) return Promise.reject(new Error("Workbench decision limit reached"));
159
- const id = randomUUID();
160
- emit({ type: "decision", id, name, args });
161
- return new Promise((resolve5, reject) => decisions.set(id, { resolve: resolve5, reject }));
196
+ const id2 = randomUUID();
197
+ emit({ type: "decision", id: id2, name, args });
198
+ return new Promise((resolve5, reject) => decisions.set(id2, { resolve: resolve5, reject }));
162
199
  };
163
200
  const server = createServer(async (request, response) => {
164
201
  if (request.method !== "POST" || request.url !== "/provider/request" || providers.size >= 4) {
@@ -175,12 +212,12 @@ async function runGuest(cli) {
175
212
  }
176
213
  const envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
177
214
  if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new Error("Invalid provider request");
178
- const id = randomUUID();
179
- providers.set(id, response);
215
+ const id2 = randomUUID();
216
+ providers.set(id2, response);
180
217
  response.once("close", () => {
181
- if (providers.delete(id)) emit({ type: "provider-cancel", id });
218
+ if (providers.delete(id2)) emit({ type: "provider-cancel", id: id2 });
182
219
  });
183
- emit({ type: "provider", id, envelope });
220
+ emit({ type: "provider", id: id2, envelope });
184
221
  } catch {
185
222
  response.writeHead(400).end("Invalid provider request");
186
223
  }
@@ -212,7 +249,7 @@ async function runGuest(cli) {
212
249
  };
213
250
  await mkdir("/home/zero/.0/controller", { recursive: true, mode: 448 });
214
251
  await writeFile("/home/zero/.0/controller/session.json", JSON.stringify(scrub(snapshot)), { mode: 384 });
215
- emit({ type: "state", snapshot: checkpoint?.harnessRoot ? { ...snapshot, checkpoint: void 0 } : snapshot });
252
+ emit({ type: "state", snapshot: { ...snapshot, ...checkpoint?.harnessRoot ? { checkpoint: void 0 } : {}, sourceContext: session.sourceContext } });
216
253
  const db = new osecDB("/home/zero/.0/controller.sqlite");
217
254
  try {
218
255
  const rows = db.getFindings(session.scanId);
@@ -373,6 +410,23 @@ async function runGuest(cli) {
373
410
  }
374
411
  });
375
412
  };
413
+ if (frame.callbacks.includes("skillRead")) callbacks.skillAuthoring = {
414
+ read: (id2) => callback("skillRead", [id2]),
415
+ create: (content) => callback("skillCreate", [content]),
416
+ update: (id2, input) => callback("skillSave", [id2, input]),
417
+ copy: (id2, input) => callback("skillCopy", [id2, input])
418
+ };
419
+ if (frame.callbacks.includes("skillEditorRead")) callbacks.skillEditor = { read: () => callback("skillEditorRead", []) };
420
+ if (frame.callbacks.includes("workflowList")) callbacks.workflowAuthoring = {
421
+ list: () => callback("workflowList", []),
422
+ save: (input) => callback("workflowSave", [input]),
423
+ ...frame.callbacks.includes("workflowEditorRead") && typeof frame.workflowEditorId === "string" ? {
424
+ editor: { id: frame.workflowEditorId, read: () => callback("workflowEditorRead", []) }
425
+ } : {}
426
+ };
427
+ if (frame.callbacks.includes("workflowInvoke")) callbacks.workflowLifecycle = {
428
+ invoke: (name, args) => callback("workflowInvoke", [name, args])
429
+ };
376
430
  if (frame.callbacks.includes("historyList")) callbacks.conversationHistory = { list: (...args) => callback("historyList", args), read: (...args) => callback("historyRead", args) };
377
431
  pluginManager = await createSessionPluginHostManager({ projectPath: "/workspace", reservedToolNames: Object.values(TOOL_DEFINITIONS).map((tool) => tool.name), coreVersion: VERSION });
378
432
  const unavailable = pluginManager.current().status().filter((entry) => entry.state !== "ready");
@@ -399,7 +453,7 @@ async function runGuest(cli) {
399
453
  if (typeof frame.text !== "string") throw new Error("Invalid operator message");
400
454
  turn = new AbortController();
401
455
  const callbacks = Object.fromEntries(EVENTS.map((name) => [name, (...args) => emit({ type: "event", name, args })]));
402
- const result = withScopeEnforcement(policy, () => session.send(frame.text, callbacks, { signal: turn.signal, generateTitle: frame.generateTitle === true }));
456
+ const result = withScopeEnforcement(policy, () => session.send(frame.text, callbacks, { signal: turn.signal, generateTitle: frame.generateTitle === true, images: frame.images }));
403
457
  await state();
404
458
  value = await result;
405
459
  turn = void 0;
@@ -514,7 +568,7 @@ async function setupWorkbench(options, home = process.env.HOME || homedir()) {
514
568
  const workspaceRoot = options.workspace === void 0 ? previous?.workspaceRoot : realpathSync(resolve(options.workspace));
515
569
  if (workspaceRoot !== void 0 && !lstatSync(workspaceRoot).isDirectory()) throw new Error("Workbench workspace must be a directory.");
516
570
  const providers = options.provider ?? previous?.providers ?? [];
517
- for (const id of providers) if (!PROVIDERS.some((provider) => provider.id === id)) throw new Error(`Unknown provider grant: ${id}. Use 0 workbench providers.`);
571
+ for (const id2 of providers) if (!PROVIDERS.some((provider) => provider.id === id2)) throw new Error(`Unknown provider grant: ${id2}. Use 0 workbench providers.`);
518
572
  const imageArchive = options.image ? resolve(options.image) : previous?.image;
519
573
  if (!imageArchive) throw new Error("No approved workbench image is available. Supply a locally provisioned archive with 0 workbench setup --image <archive>. Docker is only needed to build an image, never to run this profile.");
520
574
  const cpus = resource(options.cpus, previous?.cpus ?? 2, 64, "CPUs");
@@ -574,7 +628,7 @@ function registerWorkbenchCommand(program) {
574
628
  workbench.command("console-agent", { hidden: true }).action(async () => {
575
629
  await runWorkbenchConsoleGuest();
576
630
  });
577
- workbench.command("setup").description("Verify/provision the signed native runtime, approve a local image, and select SmolVM execution").option("--image <archive>", "Local OCI/Docker archive to digest-pin and approve; never a mutable registry tag").option("--state <directory>", "Private VM state directory (defaults to ~/.0/workbench)").option("--workspace <directory>", "Explicit workspace mount; otherwise each invocation mounts its current directory").option("--provider <id>", "Grant chatgpt-codex requests through the host provider broker; credentials stay on the host", (id, ids = []) => [...ids, id]).option("--github", "Unsupported: GitHub credential forwarding is refused").option("--no-github", "Revoke the GitHub token grant").option("--cpus <count>", "Guest virtual CPUs").option("--memory <MiB>", "Guest RAM in MiB").option("--storage <GiB>", "Guest private writable storage in GiB").option("--sandbox-image <reference=archive>", "Approve an immutable image reference for brokered isolated container actions; repeat for multiple images", (selection, selections = []) => [...selections, selection]).action(async (options) => {
631
+ workbench.command("setup").description("Verify/provision the signed native runtime, approve a local image, and select SmolVM execution").option("--image <archive>", "Local OCI/Docker archive to digest-pin and approve; never a mutable registry tag").option("--state <directory>", "Private VM state directory (defaults to ~/.0/workbench)").option("--workspace <directory>", "Explicit workspace mount; otherwise each invocation mounts its current directory").option("--provider <id>", "Grant chatgpt-codex requests through the host provider broker; credentials stay on the host", (id2, ids = []) => [...ids, id2]).option("--github", "Unsupported: GitHub credential forwarding is refused").option("--no-github", "Revoke the GitHub token grant").option("--cpus <count>", "Guest virtual CPUs").option("--memory <MiB>", "Guest RAM in MiB").option("--storage <GiB>", "Guest private writable storage in GiB").option("--sandbox-image <reference=archive>", "Approve an immutable image reference for brokered isolated container actions; repeat for multiple images", (selection, selections = []) => [...selections, selection]).action(async (options) => {
578
632
  try {
579
633
  const config = await setupWorkbench(options);
580
634
  console.log(`SmolVM workbench selected. Approved image: ${config.imageDigest}`);
@@ -606,7 +660,7 @@ function registerWorkbenchCommand(program) {
606
660
  try {
607
661
  const config = loadWorkbenchConfig();
608
662
  if (!config) throw new Error("No workbench is configured. Run 0 workbench setup first.");
609
- if (options.provider !== void 0) config.providers = options.provider === "none" ? [] : options.provider.split(",").map((id) => id.trim());
663
+ if (options.provider !== void 0) config.providers = options.provider === "none" ? [] : options.provider.split(",").map((id2) => id2.trim());
610
664
  if (options.github !== void 0) config.github = options.github;
611
665
  if (options.currentWorkspace) delete config.workspaceRoot;
612
666
  if (options.clearSandboxImages) delete config.approvedImages;
@@ -656,6 +710,771 @@ function findDashboardInstance(origin) {
656
710
  });
657
711
  }
658
712
 
713
+ // packages/cli/src/web/managed-host-reconnect.ts
714
+ function startManagedHostReconnect(options) {
715
+ const metadata = options.getMetadata ?? loadManagedWorker;
716
+ const now = options.now ?? Date.now;
717
+ const attempts = /* @__PURE__ */ new Map();
718
+ const controller = new AbortController();
719
+ let busy = false;
720
+ const tick = async () => {
721
+ if (busy || controller.signal.aborted) return;
722
+ busy = true;
723
+ try {
724
+ const registry2 = options.getRegistry();
725
+ const hosts = registry2.list().filter((host) => host.id !== "local");
726
+ for (const id2 of attempts.keys()) if (!hosts.some((host) => host.id === id2)) attempts.delete(id2);
727
+ for (const host of hosts) {
728
+ if (controller.signal.aborted) break;
729
+ try {
730
+ if (metadata(host.id).autoReconnect === false || (attempts.get(host.id)?.next ?? 0) > now()) continue;
731
+ const view = await registry2.handshake(host.id).catch(() => null);
732
+ if (view?.backend.status === "connected") {
733
+ attempts.delete(host.id);
734
+ continue;
735
+ }
736
+ if (controller.signal.aborted || !registry2.list().some((item) => item.id === host.id) || metadata(host.id).autoReconnect === false) continue;
737
+ try {
738
+ await options.repair(host.id, controller.signal);
739
+ if (!controller.signal.aborted) await options.onRecovered?.(host.id);
740
+ attempts.delete(host.id);
741
+ } catch {
742
+ const count = (attempts.get(host.id)?.count ?? 0) + 1;
743
+ attempts.set(host.id, { count, next: now() + [6e4, 12e4, 3e5][Math.min(count - 1, 2)] });
744
+ }
745
+ break;
746
+ } catch {
747
+ }
748
+ }
749
+ } catch {
750
+ } finally {
751
+ busy = false;
752
+ }
753
+ };
754
+ const timer = setInterval(() => {
755
+ void tick();
756
+ }, 3e4);
757
+ timer.unref?.();
758
+ void tick();
759
+ return () => {
760
+ clearInterval(timer);
761
+ controller.abort();
762
+ };
763
+ }
764
+
765
+ // packages/cli/src/web/local-provider-relay.ts
766
+ async function createLocalRelayProvider(selection = {}, signal) {
767
+ const [{ createWebConsoleRuntime }, { createWorkbenchProviderBroker }] = await Promise.all([
768
+ import("./operator-services-E2NONBQS.js"),
769
+ import("./dist-6RNAVFAY.js")
770
+ ]);
771
+ signal?.throwIfAborted();
772
+ if (selection.providerId && selection.providerId !== "chatgpt-codex") throw new Error("Local model relay currently supports ChatGPT Codex only. Choose the local ChatGPT connection.");
773
+ const { runtime, info } = await createWebConsoleRuntime({ ...selection, providerId: "chatgpt-codex" });
774
+ signal?.throwIfAborted();
775
+ const provider = runtime.resolvedProvider();
776
+ const model = runtime.resolvedModel();
777
+ if (provider !== "chatgpt-codex" || !model || !info.configured || !info.diagnostics.valid) throw new Error("Local model relay requires a connected ChatGPT Codex account and an explicit model.");
778
+ const routing = runtime.modelSelection();
779
+ const models = [.../* @__PURE__ */ new Set([model, ...Object.values(routing.agentModels).filter((value) => value !== "auto")])];
780
+ const broker = createWorkbenchProviderBroker({ provider, models, resolveCredentials: runtime.workbenchCredentialResolver(), signal });
781
+ return { info, grant: broker.grant, transport: { provider, models: broker.grant.models, request: (request, requestSignal) => broker.request(request, requestSignal), close: broker.close } };
782
+ }
783
+ function object(value) {
784
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
785
+ }
786
+ function id(value) {
787
+ return typeof value === "string" && value.length > 0 && value.length <= 160 && !/[\x00-\x20\x7f]/.test(value);
788
+ }
789
+ var RelayResponseError = class extends Error {
790
+ constructor(code, status) {
791
+ super("Provider relay transport failed.");
792
+ this.code = code;
793
+ this.status = status;
794
+ this.name = "RelayResponseError";
795
+ }
796
+ code;
797
+ status;
798
+ };
799
+ function safeRelayFailure(error2) {
800
+ if (error2 instanceof RelayResponseError) return { name: error2.name, code: error2.code };
801
+ const name = error2 instanceof Error && ["Error", "TypeError", "SyntaxError", "AbortError", "TimeoutError", "BackendConnectionError"].includes(error2.name) ? error2.name : "OtherError";
802
+ const known = {
803
+ "Provider request cancelled or timed out": "provider_timeout_or_cancel",
804
+ "Provider response stream failed": "provider_stream_failed",
805
+ "Host provider request failed": "provider_request_failed",
806
+ "Provider broker concurrency limit reached": "provider_concurrency",
807
+ "Provider broker session budget exhausted": "provider_budget",
808
+ "Provider broker is closed": "provider_closed",
809
+ "Provider or model is not granted to this workbench": "provider_grant_rejected",
810
+ "Provider request exceeds its byte limit": "provider_input_limit",
811
+ "Invalid provider request JSON": "provider_input_json",
812
+ "Provider broker permits only stateless streamed Responses requests": "provider_body_shape",
813
+ "Provider broker refuses hosted tools and external tool destinations": "provider_tool_destination",
814
+ "Unsupported provider input item": "provider_input_type",
815
+ "Provider broker permits text input only": "provider_text_only",
816
+ "Remote provider relay scope changed.": "remote_scope_changed",
817
+ "Invalid provider cancellation.": "remote_cancel_invalid",
818
+ "Remote provider request exceeds its approval.": "remote_request_not_approved",
819
+ "Remote provider relay exceeded its request budget.": "relay_budget",
820
+ "Provider output exceeds approval.": "provider_output_limit",
821
+ "Provider SSE event exceeds response limit.": "provider_sse_limit",
822
+ "Backend response identity does not match the registered engine.": "remote_identity_changed",
823
+ "Backend response exceeds the transport limit.": "remote_response_limit",
824
+ "Backend request timed out.": "remote_timeout",
825
+ "Backend transport failed.": "remote_transport_failed",
826
+ "Backend request capacity exceeded.": "remote_capacity"
827
+ };
828
+ return { name, code: error2 instanceof Error ? Object.hasOwn(known, error2.message) ? known[error2.message] : "unclassified" : "unclassified" };
829
+ }
830
+ async function json(response) {
831
+ if (!response.ok) {
832
+ await response.body?.cancel();
833
+ throw new RelayResponseError("remote_http_rejected", response.status);
834
+ }
835
+ try {
836
+ return object(await response.json());
837
+ } catch {
838
+ throw new RelayResponseError("remote_invalid_json", response.status);
839
+ }
840
+ }
841
+ async function pause(signal, delayMs = 500) {
842
+ if (signal.aborted) return;
843
+ await new Promise((resolve5) => {
844
+ const abort = () => {
845
+ clearTimeout(timer);
846
+ resolve5();
847
+ };
848
+ const timer = setTimeout(() => {
849
+ signal.removeEventListener("abort", abort);
850
+ resolve5();
851
+ }, delayMs);
852
+ signal.addEventListener("abort", abort, { once: true });
853
+ });
854
+ }
855
+ function retryablePollFailure(error2) {
856
+ if (error2 instanceof RelayResponseError) return [408, 425, 429, 500, 502, 503, 504].includes(error2.status);
857
+ if (!(error2 instanceof Error) || error2.name !== "BackendConnectionError") return false;
858
+ const status = error2.statusCode;
859
+ return status === 504 && error2.message === "Backend request timed out." || status === 502 && error2.message === "Backend transport failed." || status === 429 && error2.message === "Backend request capacity exceeded.";
860
+ }
861
+ function failureTransportStatus(error2, fallback) {
862
+ if (error2 instanceof RelayResponseError) return error2.status;
863
+ if (error2 instanceof Error && error2.name === "BackendConnectionError" && typeof error2.statusCode === "number") return error2.statusCode;
864
+ return fallback;
865
+ }
866
+ function providerSseCompletion(maximumBytes = 8 * 1024 * 1024) {
867
+ const decoder2 = new TextDecoder();
868
+ let buffer = "", completed = false;
869
+ return (chunk) => {
870
+ if (completed) return true;
871
+ buffer += decoder2.decode(chunk, { stream: true });
872
+ let boundary;
873
+ while (boundary = /\r?\n\r?\n/.exec(buffer)) {
874
+ const block = buffer.slice(0, boundary.index);
875
+ if (Buffer.byteLength(block) > maximumBytes) throw new Error("Provider SSE event exceeds response limit.");
876
+ buffer = buffer.slice(boundary.index + boundary[0].length);
877
+ const data = block.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
878
+ if (!data) continue;
879
+ if (data === "[DONE]") {
880
+ completed = true;
881
+ break;
882
+ }
883
+ try {
884
+ const event = JSON.parse(data);
885
+ if (event && ["response.completed", "response.failed", "response.incomplete", "error"].includes(String(event.type))) {
886
+ completed = true;
887
+ break;
888
+ }
889
+ } catch {
890
+ }
891
+ }
892
+ if (Buffer.byteLength(buffer) > maximumBytes) throw new Error("Provider SSE event exceeds response limit.");
893
+ return completed;
894
+ };
895
+ }
896
+ async function createLocalProviderRelay(options) {
897
+ if (options.backendId === "local" || !id(options.backendId)) throw new Error("Provider relay requires a registered remote host.");
898
+ const lifetime = new AbortController();
899
+ const signal = options.signal ? AbortSignal.any([lifetime.signal, options.signal]) : lifetime.signal;
900
+ signal.throwIfAborted();
901
+ const peer = await options.registry.handshake(options.backendId);
902
+ const epoch2 = peer.handshake?.serverInstanceId;
903
+ if (peer.backend.status !== "connected" || !id(epoch2)) throw new Error("Provider relay requires a connected, identified host.");
904
+ const local = await (options.createProvider ?? createLocalRelayProvider)(options.selection ?? {}, signal);
905
+ const base = "/api/console/provider-relays";
906
+ let grant;
907
+ try {
908
+ const response = await json(await options.registry.request(options.backendId, base, { method: "POST", body: { epoch: epoch2, provider: local.grant.provider, models: local.grant.models, model: local.info.model }, signal }));
909
+ const candidate = object(response.grant);
910
+ if (!id(candidate.id) || candidate.epoch !== epoch2 || candidate.provider !== local.grant.provider || !Array.isArray(candidate.models) || candidate.models.length !== local.grant.models.length || new Set(candidate.models).size !== candidate.models.length || candidate.models.some((model) => typeof model !== "string" || !local.grant.models.includes(model)) || candidate.model !== local.info.model || typeof candidate.expiresAt !== "string" || !Number.isFinite(Date.parse(candidate.expiresAt))) throw new Error("Remote provider relay grant does not match the local approval.");
911
+ grant = candidate;
912
+ } catch (error2) {
913
+ await local.transport.close?.();
914
+ throw error2;
915
+ }
916
+ const diagnostic = { phase: "registered", lastPollAt: null, seenRequests: 0, activeRequests: 0, bytesForwarded: 0 };
917
+ const grantPath = `${base}/${encodeURIComponent(grant.id)}`;
918
+ let revocation;
919
+ const revokeGrant = () => revocation ??= options.registry.request(options.backendId, grantPath, { method: "DELETE", signal: AbortSignal.timeout(5e3) }).then(async (response) => {
920
+ await response.body?.cancel();
921
+ }).catch(() => {
922
+ });
923
+ const active = /* @__PURE__ */ new Map();
924
+ const seen = /* @__PURE__ */ new Set();
925
+ const tasks = /* @__PURE__ */ new Set();
926
+ let boundSession, polling, closing;
927
+ const abortActive = () => {
928
+ for (const controller of active.values()) controller.abort();
929
+ };
930
+ signal.addEventListener("abort", abortActive, { once: true });
931
+ const remoteRequest = async (path, optionsValue) => {
932
+ const response = await options.registry.request(options.backendId, path, optionsValue);
933
+ diagnostic.lastTransportStatus = response.status;
934
+ return response;
935
+ };
936
+ const send = (request, events, callerSignal) => remoteRequest(`${grantPath}/requests/${encodeURIComponent(request.requestId)}/events`, { method: "POST", body: { epoch: epoch2, sessionId: boundSession, events }, signal: callerSignal }).then(json);
937
+ async function service(request) {
938
+ const controller = new AbortController();
939
+ active.set(request.requestId, controller);
940
+ diagnostic.phase = "requesting";
941
+ diagnostic.activeRequests = active.size;
942
+ delete diagnostic.error;
943
+ const requestSignal = AbortSignal.any([signal, controller.signal, AbortSignal.timeout(local.grant.limits.timeoutMs)]);
944
+ let reader;
945
+ const cancelReader = () => {
946
+ void reader?.cancel().catch(() => {
947
+ });
948
+ };
949
+ requestSignal.addEventListener("abort", cancelReader, { once: true });
950
+ let stage = "provider_request";
951
+ try {
952
+ const response = await local.transport.request({ provider: request.provider, model: request.model, body: request.body }, requestSignal);
953
+ diagnostic.lastUpstreamStatus = response.status;
954
+ stage = "headers_post";
955
+ await send(request, [{ kind: "headers", status: response.status, contentType: response.headers.get("content-type") || "text/event-stream" }], requestSignal);
956
+ diagnostic.phase = "streaming";
957
+ if (response.body) {
958
+ reader = response.body.getReader();
959
+ let bytes = 0;
960
+ const completed = response.headers.get("content-type")?.toLowerCase().includes("text/event-stream") ? providerSseCompletion(local.grant.limits.maxResponseBytes) : void 0;
961
+ while (!requestSignal.aborted) {
962
+ stage = "reader_read";
963
+ const chunk = await reader.read();
964
+ if (chunk.done) break;
965
+ bytes += chunk.value.byteLength;
966
+ if (bytes > local.grant.limits.maxResponseBytes) throw new Error("Provider output exceeds approval.");
967
+ stage = "chunk_post";
968
+ for (let offset = 0; offset < chunk.value.byteLength; offset += 32 * 1024) {
969
+ const portion = chunk.value.subarray(offset, offset + 32 * 1024);
970
+ await send(request, [{ kind: "chunk", data: Buffer.from(portion).toString("base64") }], requestSignal);
971
+ diagnostic.bytesForwarded += portion.byteLength;
972
+ }
973
+ stage = "sse_parse";
974
+ if (completed?.(chunk.value)) break;
975
+ }
976
+ requestSignal.throwIfAborted();
977
+ }
978
+ stage = "end_post";
979
+ await send(request, [{ kind: "end" }], requestSignal);
980
+ } catch (error2) {
981
+ if (!signal.aborted && !controller.signal.aborted) {
982
+ const failure = safeRelayFailure(error2);
983
+ diagnostic.lastFailureStage = stage;
984
+ diagnostic.lastFailureName = failure.name;
985
+ diagnostic.lastFailureCode = failure.code;
986
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
987
+ diagnostic.error = diagnostic.phase === "requesting" ? "upstream_failed" : "response_rejected";
988
+ await send(request, [{ kind: "error" }], AbortSignal.any([signal, AbortSignal.timeout(5e3)])).catch(() => {
989
+ });
990
+ }
991
+ } finally {
992
+ requestSignal.removeEventListener("abort", cancelReader);
993
+ controller.abort();
994
+ if (reader) {
995
+ await reader.cancel().catch(() => {
996
+ });
997
+ reader.releaseLock();
998
+ }
999
+ active.delete(request.requestId);
1000
+ diagnostic.activeRequests = active.size;
1001
+ if (!signal.aborted && !active.size) diagnostic.phase = "polling";
1002
+ }
1003
+ }
1004
+ async function poll() {
1005
+ let failureCategory = "poll_rejected";
1006
+ try {
1007
+ if (!active.size) diagnostic.phase = "polling";
1008
+ while (!signal.aborted) {
1009
+ failureCategory = "poll_rejected";
1010
+ let body;
1011
+ try {
1012
+ body = await json(await remoteRequest(`${grantPath}/requests`, { method: "GET", signal }));
1013
+ } catch (error2) {
1014
+ if (signal.aborted || !retryablePollFailure(error2)) throw error2;
1015
+ const failure = safeRelayFailure(error2);
1016
+ diagnostic.phase = "reconnecting";
1017
+ diagnostic.error = "poll_rejected";
1018
+ diagnostic.lastFailureStage = "poll";
1019
+ diagnostic.lastFailureName = failure.name;
1020
+ diagnostic.lastFailureCode = failure.code;
1021
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
1022
+ diagnostic.consecutivePollFailures = (diagnostic.consecutivePollFailures ?? 0) + 1;
1023
+ await pause(signal, Math.min(5e3, 500 * 2 ** Math.min(diagnostic.consecutivePollFailures - 1, 4)));
1024
+ continue;
1025
+ }
1026
+ diagnostic.consecutivePollFailures = 0;
1027
+ if (diagnostic.error === "poll_rejected") delete diagnostic.error;
1028
+ diagnostic.phase = active.size ? "streaming" : "polling";
1029
+ diagnostic.lastPollAt = (/* @__PURE__ */ new Date()).toISOString();
1030
+ failureCategory = "scope_changed";
1031
+ if (body.grantId !== grant.id || body.epoch !== epoch2 || body.sessionId !== void 0 && body.sessionId !== boundSession || !Array.isArray(body.requests) || !Array.isArray(body.cancelled) || body.requests.length > 4 || body.cancelled.length > 256) throw new Error("Remote provider relay scope changed.");
1032
+ for (const requestId of body.cancelled) {
1033
+ if (!id(requestId)) throw new Error("Invalid provider cancellation.");
1034
+ active.get(requestId)?.abort();
1035
+ }
1036
+ for (const value of body.requests) {
1037
+ const request = object(value);
1038
+ failureCategory = "request_rejected";
1039
+ if (!id(request.requestId) || request.grantId !== grant.id || request.epoch !== epoch2 || request.sessionId !== boundSession || request.provider !== local.grant.provider || typeof request.model !== "string" || !local.grant.models.includes(request.model) || typeof request.body !== "string" || Buffer.byteLength(request.body) > local.grant.limits.maxRequestBytes) throw new Error("Remote provider request exceeds its approval.");
1040
+ if (seen.has(request.requestId)) continue;
1041
+ if (seen.size >= local.grant.limits.maxRequests || active.size >= Math.min(4, local.grant.limits.maxConcurrent)) throw new Error("Remote provider relay exceeded its request budget.");
1042
+ seen.add(request.requestId);
1043
+ diagnostic.seenRequests = seen.size;
1044
+ const task = service(request);
1045
+ tasks.add(task);
1046
+ void task.finally(() => tasks.delete(task));
1047
+ }
1048
+ await pause(signal);
1049
+ }
1050
+ } catch (error2) {
1051
+ if (!signal.aborted) {
1052
+ diagnostic.phase = "failed";
1053
+ diagnostic.error = failureCategory;
1054
+ const failure = safeRelayFailure(error2);
1055
+ diagnostic.lastFailureStage = "poll";
1056
+ diagnostic.lastFailureName = failure.name;
1057
+ diagnostic.lastFailureCode = failure.code;
1058
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
1059
+ }
1060
+ if (!signal.aborted) options.onError?.("The local model connection to this host ended. Reconnect the host before continuing.");
1061
+ lifetime.abort();
1062
+ } finally {
1063
+ if (signal.aborted && diagnostic.phase !== "failed") diagnostic.phase = "closed";
1064
+ abortActive();
1065
+ await local.transport.close?.();
1066
+ await revokeGrant();
1067
+ }
1068
+ }
1069
+ return {
1070
+ grant,
1071
+ info: local.info,
1072
+ status: () => ({ ...diagnostic }),
1073
+ start(sessionId) {
1074
+ if (!id(sessionId) || signal.aborted || boundSession) throw new Error("Provider relay must bind once to an active remote session.");
1075
+ boundSession = sessionId;
1076
+ polling = poll();
1077
+ },
1078
+ close() {
1079
+ return closing ??= (async () => {
1080
+ diagnostic.phase = "closed";
1081
+ lifetime.abort();
1082
+ abortActive();
1083
+ signal.removeEventListener("abort", abortActive);
1084
+ await local.transport.close?.();
1085
+ await Promise.allSettled([...tasks, ...polling ? [polling] : []]);
1086
+ await revokeGrant();
1087
+ })();
1088
+ }
1089
+ };
1090
+ }
1091
+
1092
+ // packages/cli/src/web/local-provider-sessions.ts
1093
+ var LocalProviderSessionError = class extends Error {
1094
+ constructor(message2, statusCode = 400) {
1095
+ super(message2);
1096
+ this.statusCode = statusCode;
1097
+ }
1098
+ statusCode;
1099
+ };
1100
+ var resourceId = external_exports.string().min(1).max(160).regex(/^[^\x00-\x20\x7f]+$/);
1101
+ var runtimeSchema = external_exports.object({ providerId: external_exports.string().min(1).max(256).optional(), model: external_exports.string().min(1).max(256).optional(), reasoningEffort: external_exports.string().min(1).max(256).optional(), agentModels: external_exports.record(external_exports.string().min(1).max(64), external_exports.string().min(1).max(256)).refine((value) => Object.keys(value).length <= 32).optional(), singleModel: external_exports.boolean().optional(), autoRoute: external_exports.boolean().optional() }).strict();
1102
+ var createSchema = external_exports.object({ workflowId: external_exports.string().min(1).max(128).optional(), target: external_exports.string().max(4096).optional(), title: external_exports.string().max(200).optional(), role: external_exports.enum(["discovery", "attack", "verify", "report", "audit", "review"]).optional(), autonomyMode: external_exports.enum(["standard", "recon", "copilot", "yolo"]).optional(), scope: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional(), runtime: runtimeSchema.optional(), findingId: external_exports.string().max(256).optional(), findingIntent: external_exports.enum(["investigate", "verify", "draft_fix", "impact"]).optional() }).strict();
1103
+ var LocalProviderSessions = class {
1104
+ constructor(options) {
1105
+ this.options = options;
1106
+ }
1107
+ options;
1108
+ sessions = /* @__PURE__ */ new Map();
1109
+ lifetime = new AbortController();
1110
+ creating = 0;
1111
+ reconnecting = /* @__PURE__ */ new Set();
1112
+ key(backendId, sessionId) {
1113
+ return JSON.stringify([backendId, sessionId]);
1114
+ }
1115
+ signal() {
1116
+ return this.options.signal ? AbortSignal.any([this.lifetime.signal, this.options.signal]) : this.lifetime.signal;
1117
+ }
1118
+ assertBackend(backendId) {
1119
+ if (!resourceId.safeParse(backendId).success || backendId === "local") throw new LocalProviderSessionError("Choose a registered remote host.");
1120
+ this.signal().throwIfAborted();
1121
+ }
1122
+ async request(backendId, path, method, body) {
1123
+ const response = await this.options.registry.request(backendId, path, { method, body, signal: this.signal() });
1124
+ if (!response.ok) {
1125
+ await response.body?.cancel();
1126
+ throw new LocalProviderSessionError("The remote host rejected this conversation change.", response.status);
1127
+ }
1128
+ const value = await response.json();
1129
+ if (!value?.session || !resourceId.safeParse(value.session.id).success) throw new LocalProviderSessionError("The remote host returned an invalid conversation.", 502);
1130
+ return value.session;
1131
+ }
1132
+ async create(backendId, input, ownerId = "local") {
1133
+ this.assertBackend(backendId);
1134
+ const parsed = createSchema.safeParse(input);
1135
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid remote conversation configuration; model grants are issued locally.");
1136
+ if (this.sessions.size + this.creating >= 32) throw new LocalProviderSessionError("Close a local model connection before creating another remote conversation.", 429);
1137
+ this.creating++;
1138
+ let relay;
1139
+ try {
1140
+ relay = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection: parsed.data.runtime, signal: this.signal() });
1141
+ const session = await this.request(backendId, "/api/console/sessions", "POST", { ...parsed.data, runtime: { ...parsed.data.runtime, providerId: relay.info.providerId, model: relay.info.model }, modelSource: { kind: "local", grantId: relay.grant.id } });
1142
+ this.signal().throwIfAborted();
1143
+ const key = this.key(backendId, session.id);
1144
+ if (this.sessions.has(key)) throw new LocalProviderSessionError("The host reused an existing conversation ID.", 409);
1145
+ relay.start(session.id);
1146
+ this.sessions.set(key, { owner: ownerId, relay, selection: parsed.data.runtime ?? {}, configuring: false });
1147
+ return { session };
1148
+ } catch (error2) {
1149
+ await relay?.close();
1150
+ throw error2;
1151
+ } finally {
1152
+ this.creating--;
1153
+ }
1154
+ }
1155
+ async configure(backendId, sessionId, runtime, ownerId = "local") {
1156
+ this.assertBackend(backendId);
1157
+ if (!resourceId.safeParse(sessionId).success) throw new LocalProviderSessionError("Invalid conversation ID.");
1158
+ const managed = this.sessions.get(this.key(backendId, sessionId));
1159
+ if (!managed) return this.reconnect(backendId, sessionId, runtime, ownerId);
1160
+ if (managed.owner !== ownerId) throw new LocalProviderSessionError("This local model connection does not belong to the current workspace.", 404);
1161
+ const parsed = runtimeSchema.safeParse(runtime);
1162
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid local model selection.");
1163
+ if (managed.configuring) throw new LocalProviderSessionError("Wait for the current model change.", 409);
1164
+ managed.configuring = true;
1165
+ let replacement;
1166
+ try {
1167
+ const selection = { ...managed.selection, ...parsed.data };
1168
+ replacement = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection, signal: this.signal() });
1169
+ const session = await this.request(backendId, `/api/console/sessions/${encodeURIComponent(sessionId)}/configuration`, "PATCH", { runtime: { ...selection, providerId: replacement.info.providerId, model: replacement.info.model }, modelSource: { kind: "local", grantId: replacement.grant.id } });
1170
+ if (session.id !== sessionId) throw new LocalProviderSessionError("The host returned a different conversation.", 409);
1171
+ this.signal().throwIfAborted();
1172
+ replacement.start(sessionId);
1173
+ const previous = managed.relay;
1174
+ managed.relay = replacement;
1175
+ managed.selection = selection;
1176
+ replacement = void 0;
1177
+ await previous.close();
1178
+ return { session };
1179
+ } finally {
1180
+ managed.configuring = false;
1181
+ await replacement?.close();
1182
+ }
1183
+ }
1184
+ /** Explicitly restore a saved local-model marker after either engine restarted. */
1185
+ async reconnect(backendId, sessionId, runtime, ownerId = "local") {
1186
+ this.assertBackend(backendId);
1187
+ if (!resourceId.safeParse(sessionId).success) throw new LocalProviderSessionError("Invalid conversation ID.");
1188
+ const key = this.key(backendId, sessionId), existing = this.sessions.get(key);
1189
+ if (existing) return this.configure(backendId, sessionId, runtime, ownerId);
1190
+ const parsed = runtimeSchema.safeParse(runtime);
1191
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid local model selection.");
1192
+ if (this.reconnecting.has(key)) throw new LocalProviderSessionError("Wait for this conversation to reconnect.", 409);
1193
+ if (this.sessions.size + this.creating >= 32) throw new LocalProviderSessionError("Close a local model connection before reconnecting another conversation.", 429);
1194
+ this.reconnecting.add(key);
1195
+ this.creating++;
1196
+ let replacement;
1197
+ try {
1198
+ const response = await this.options.registry.request(backendId, `/api/console/sessions/${encodeURIComponent(sessionId)}`, { method: "GET", signal: this.signal() });
1199
+ if (!response.ok) {
1200
+ await response.body?.cancel();
1201
+ throw new LocalProviderSessionError("This remote conversation could not be reopened.", response.status);
1202
+ }
1203
+ const snapshot = (await response.json()).snapshot;
1204
+ if (snapshot?.session?.id !== sessionId || snapshot.runtime?.modelSource !== "local") throw new LocalProviderSessionError("Only an existing local-model conversation can reconnect through this route.", 409);
1205
+ if (snapshot.session.status !== "ready") throw new LocalProviderSessionError("Wait until this remote conversation is idle before reconnecting its model.", 409);
1206
+ const selection = runtimeSchema.parse({ providerId: "chatgpt-codex", ...snapshot.runtime.model ? { model: snapshot.runtime.model } : {}, ...parsed.data });
1207
+ replacement = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection, signal: this.signal() });
1208
+ const session = await this.request(backendId, `/api/console/sessions/${encodeURIComponent(sessionId)}/configuration`, "PATCH", { runtime: { ...selection, providerId: replacement.info.providerId, model: replacement.info.model }, modelSource: { kind: "local", grantId: replacement.grant.id } });
1209
+ if (session.id !== sessionId) throw new LocalProviderSessionError("The host returned a different conversation.", 409);
1210
+ this.signal().throwIfAborted();
1211
+ replacement.start(sessionId);
1212
+ this.sessions.set(key, { owner: ownerId, relay: replacement, selection, configuring: false });
1213
+ replacement = void 0;
1214
+ return { session };
1215
+ } finally {
1216
+ this.creating--;
1217
+ this.reconnecting.delete(key);
1218
+ await replacement?.close();
1219
+ }
1220
+ }
1221
+ async status(backendId, sessionId, ownerId = "local") {
1222
+ this.assertBackend(backendId);
1223
+ const managed = this.sessions.get(this.key(backendId, sessionId));
1224
+ if (!managed || managed.owner !== ownerId) throw new LocalProviderSessionError("This local model connection does not belong to the current workspace.", 404);
1225
+ const local = managed.relay.status();
1226
+ try {
1227
+ const response = await this.options.registry.request(backendId, `/api/console/provider-relays/${encodeURIComponent(managed.relay.grant.id)}/status`, { method: "GET", signal: AbortSignal.any([this.signal(), AbortSignal.timeout(5e3)]) });
1228
+ if (!response.ok) {
1229
+ await response.body?.cancel();
1230
+ return { local, remote: { available: false, status: response.status } };
1231
+ }
1232
+ const body = await response.json();
1233
+ if (body.grantId !== managed.relay.grant.id || body.epoch !== managed.relay.grant.epoch || body.sessionId !== sessionId) throw new Error("scope");
1234
+ const pending = external_exports.array(external_exports.object({ delivered: external_exports.boolean(), headers: external_exports.boolean(), receivedBytes: external_exports.number().finite().nonnegative(), bufferedBytes: external_exports.number().finite().nonnegative(), ageMs: external_exports.number().finite().nonnegative() }).strip()).max(4).parse(body.pending);
1235
+ const cancelledCount = external_exports.number().int().nonnegative().parse(body.cancelledCount);
1236
+ return { local, remote: { available: true, pending, cancelledCount } };
1237
+ } catch {
1238
+ return { local, remote: { available: false } };
1239
+ }
1240
+ }
1241
+ async release(backendId, sessionId, ownerId = "local") {
1242
+ const key = this.key(backendId, sessionId), managed = this.sessions.get(key);
1243
+ if (!managed || managed.owner !== ownerId) return;
1244
+ this.sessions.delete(key);
1245
+ await managed.relay.close();
1246
+ }
1247
+ async dispose() {
1248
+ this.lifetime.abort();
1249
+ const entries = [...this.sessions.values()];
1250
+ this.sessions.clear();
1251
+ await Promise.allSettled(entries.map((entry) => entry.relay.close()));
1252
+ }
1253
+ };
1254
+
1255
+ // packages/cli/src/web/remote-provider-relay.ts
1256
+ import { randomUUID as randomUUID2 } from "node:crypto";
1257
+ var ProviderRelayError = class extends Error {
1258
+ constructor(message2, statusCode = 400) {
1259
+ super(message2);
1260
+ this.statusCode = statusCode;
1261
+ }
1262
+ statusCode;
1263
+ };
1264
+ var GRANT_IDLE_MS = 30 * 6e4;
1265
+ var REDELIVER_UNACKNOWLEDGED_MS = 5e3;
1266
+ var modelId = external_exports.string().regex(/^[A-Za-z0-9._-]{1,128}$/);
1267
+ var grantInput = external_exports.object({ epoch: external_exports.string().min(1).max(256), provider: external_exports.literal("chatgpt-codex"), models: external_exports.array(modelId).min(1).max(16), model: modelId }).strict();
1268
+ var eventsInput = external_exports.object({ epoch: external_exports.string(), sessionId: external_exports.string().min(1).max(256), events: external_exports.array(external_exports.discriminatedUnion("kind", [
1269
+ external_exports.object({ kind: external_exports.literal("headers"), status: external_exports.number().int().min(200).max(599).refine((status) => ![204, 205, 304].includes(status)), contentType: external_exports.string().max(128).regex(/^(text\/event-stream|application\/json)(?:;[^\r\n]*)?$/i).transform((value) => value.split(";")[0].toLowerCase()) }).strict(),
1270
+ external_exports.object({ kind: external_exports.literal("chunk"), data: external_exports.string().max(65536).regex(/^[A-Za-z0-9+/]*={0,2}$/) }).strict(),
1271
+ external_exports.object({ kind: external_exports.literal("end") }).strict(),
1272
+ external_exports.object({ kind: external_exports.literal("error") }).strict()
1273
+ ])).min(1).max(16) }).strict();
1274
+ var RemoteProviderRelayBroker = class {
1275
+ epoch;
1276
+ #grants = /* @__PURE__ */ new Map();
1277
+ #disposed = false;
1278
+ constructor(options) {
1279
+ this.epoch = options.epoch;
1280
+ }
1281
+ create(input) {
1282
+ const value = grantInput.parse(input);
1283
+ if (this.#disposed || value.epoch !== this.epoch) throw new ProviderRelayError("Provider relay engine epoch changed.", 409);
1284
+ if (!value.models.includes(value.model) || new Set(value.models).size !== value.models.length) throw new ProviderRelayError("Provider relay requires an exact model allowlist.");
1285
+ for (const [id2, state] of this.#grants) if (Date.parse(state.grant.expiresAt) <= Date.now()) this.close(id2);
1286
+ if (this.#grants.size >= 32) throw new ProviderRelayError("Provider relay grant limit reached.", 429);
1287
+ const grant = { id: randomUUID2(), epoch: this.epoch, provider: value.provider, models: value.models, model: value.model, expiresAt: new Date(Date.now() + GRANT_IDLE_MS).toISOString() };
1288
+ this.#grants.set(grant.id, { grant, requests: /* @__PURE__ */ new Map(), cancelled: [] });
1289
+ return structuredClone(grant);
1290
+ }
1291
+ #get(id2) {
1292
+ const state = this.#grants.get(id2);
1293
+ if (!state || this.#disposed) throw new ProviderRelayError("Provider relay grant is unavailable. Reconnect the local model source.", 410);
1294
+ if (Date.parse(state.grant.expiresAt) <= Date.now()) {
1295
+ this.close(id2);
1296
+ throw new ProviderRelayError("Provider relay grant expired. Reconnect the local model source.", 410);
1297
+ }
1298
+ return state;
1299
+ }
1300
+ bind(id2, sessionId) {
1301
+ const state = this.#get(id2);
1302
+ if (state.sessionId && state.sessionId !== sessionId) throw new ProviderRelayError("Provider relay grant belongs to another conversation.", 409);
1303
+ state.sessionId = sessionId;
1304
+ return structuredClone(state.grant);
1305
+ }
1306
+ transport(id2, sessionId) {
1307
+ const grant = this.bind(id2, sessionId);
1308
+ return { provider: grant.provider, models: grant.models, identity: `relay:${grant.epoch}:${grant.id}`, request: (envelope, signal) => this.request(id2, sessionId, envelope, signal) };
1309
+ }
1310
+ runtime(id2, sessionId, selection = {}) {
1311
+ const grant = this.bind(id2, sessionId);
1312
+ if (selection.providerId && selection.providerId !== grant.provider) throw new ProviderRelayError("Provider is outside the local model grant.", 403);
1313
+ return new LlmApiRuntime({
1314
+ type: "api",
1315
+ timeout: 12e4,
1316
+ model: selection.model ?? grant.model,
1317
+ provider: "chatgpt-codex",
1318
+ singleModel: selection.singleModel,
1319
+ agentModels: selection.agentModels,
1320
+ autoRoute: selection.autoRoute,
1321
+ providerTransport: this.transport(id2, sessionId)
1322
+ });
1323
+ }
1324
+ request(id2, sessionId, envelope, signal) {
1325
+ const state = this.#get(id2);
1326
+ if (state.sessionId !== sessionId || envelope.provider !== state.grant.provider || !state.grant.models.includes(envelope.model)) throw new ProviderRelayError("Request is outside the local model grant.", 403);
1327
+ signal?.throwIfAborted();
1328
+ if (state.requests.size >= 4) throw new ProviderRelayError("Local model request limit reached.", 429);
1329
+ if (Buffer.byteLength(envelope.body) > 4 * 1024 * 1024) throw new ProviderRelayError("Local model request exceeds 4 MiB.", 413);
1330
+ const body = JSON.parse(envelope.body);
1331
+ if (body.model !== envelope.model || body.background === true || body.store === true || !Array.isArray(body.input)) throw new ProviderRelayError("Invalid granted Responses request.");
1332
+ return new Promise((resolve5, reject) => {
1333
+ const requestId = randomUUID2();
1334
+ const cancel = () => this.#finish(state, pending, new Error("Local provider request cancelled."));
1335
+ const pending = {
1336
+ ...envelope,
1337
+ requestId,
1338
+ grantId: id2,
1339
+ epoch: this.epoch,
1340
+ sessionId,
1341
+ startedAt: Date.now(),
1342
+ delivered: false,
1343
+ received: 0,
1344
+ buffered: 0,
1345
+ ended: false,
1346
+ headers: false,
1347
+ queue: [],
1348
+ resolve: resolve5,
1349
+ reject,
1350
+ timer: setTimeout(() => this.#finish(state, pending, new Error("Local provider relay timed out.")), 12e4),
1351
+ cleanup: () => signal?.removeEventListener("abort", cancel)
1352
+ };
1353
+ signal?.addEventListener("abort", cancel, { once: true });
1354
+ state.requests.set(requestId, pending);
1355
+ if (signal?.aborted) cancel();
1356
+ });
1357
+ }
1358
+ status(id2) {
1359
+ const state = this.#get(id2);
1360
+ return {
1361
+ grantId: id2,
1362
+ epoch: this.epoch,
1363
+ sessionId: state.sessionId,
1364
+ expiresAt: state.grant.expiresAt,
1365
+ pending: [...state.requests.values()].map((request) => ({ id: request.requestId, delivered: request.delivered, headers: request.headers, receivedBytes: request.received, bufferedBytes: request.buffered, ageMs: Math.max(0, Date.now() - request.startedAt) })),
1366
+ cancelledCount: state.cancelled.length
1367
+ };
1368
+ }
1369
+ poll(id2) {
1370
+ const state = this.#get(id2);
1371
+ state.grant.expiresAt = new Date(Date.now() + GRANT_IDLE_MS).toISOString();
1372
+ const requests = [...state.requests.values()];
1373
+ const next = requests.find((request) => !request.delivered) ?? requests.find((request) => !request.headers && Date.now() - (request.deliveredAt ?? request.startedAt) >= REDELIVER_UNACKNOWLEDGED_MS);
1374
+ if (next) {
1375
+ next.delivered = true;
1376
+ next.deliveredAt = Date.now();
1377
+ }
1378
+ const cancelled = state.cancelled.splice(0);
1379
+ return { grantId: id2, epoch: this.epoch, sessionId: state.sessionId, expiresAt: state.grant.expiresAt, requests: next ? [{ requestId: next.requestId, grantId: id2, epoch: this.epoch, sessionId: next.sessionId, provider: next.provider, model: next.model, body: next.body }] : [], cancelled };
1380
+ }
1381
+ #finish(state, request, error2) {
1382
+ if (request.ended) return;
1383
+ request.ended = true;
1384
+ clearTimeout(request.timer);
1385
+ request.cleanup();
1386
+ state.requests.delete(request.requestId);
1387
+ if (error2) {
1388
+ state.cancelled.push(request.requestId);
1389
+ state.cancelled = state.cancelled.slice(-64);
1390
+ if (!request.headers) request.reject(error2);
1391
+ request.controller?.error(error2);
1392
+ } else if (!request.headers) request.reject(new Error("Provider relay ended without response headers."));
1393
+ else if (request.queue.length === 0) request.controller?.close();
1394
+ }
1395
+ events(id2, requestId, input) {
1396
+ if (Buffer.byteLength(JSON.stringify(input)) > 512 * 1024) throw new ProviderRelayError("Provider event batch exceeds 512 KiB.", 413);
1397
+ const state = this.#get(id2);
1398
+ const value = eventsInput.parse(input);
1399
+ if (value.epoch !== this.epoch || value.sessionId !== state.sessionId) throw new ProviderRelayError("Provider relay ownership or epoch changed.", 409);
1400
+ const pending = state.requests.get(requestId);
1401
+ if (!pending) throw new ProviderRelayError("Provider request is no longer pending.", 410);
1402
+ for (const event of value.events) {
1403
+ if (pending.ended) throw new ProviderRelayError("Provider response has ended.", 409);
1404
+ if (event.kind === "headers") {
1405
+ if (pending.headers) throw new ProviderRelayError("Duplicate provider response headers.", 409);
1406
+ pending.headers = true;
1407
+ const stream = new ReadableStream({ start: (controller) => {
1408
+ pending.controller = controller;
1409
+ }, pull: (controller) => {
1410
+ const chunk = pending.queue.shift();
1411
+ if (chunk) {
1412
+ pending.buffered -= chunk.byteLength;
1413
+ controller.enqueue(chunk);
1414
+ } else if (pending.ended) controller.close();
1415
+ }, cancel: () => this.#finish(state, pending, new Error("Provider response reader cancelled.")) });
1416
+ pending.resolve(new Response(stream, { status: event.status, headers: { "content-type": event.contentType } }));
1417
+ } else if (event.kind === "chunk") {
1418
+ if (!pending.headers) throw new ProviderRelayError("Provider headers must precede body chunks.", 409);
1419
+ const bytes = Buffer.from(event.data, "base64");
1420
+ if (bytes.toString("base64") !== event.data || bytes.byteLength > 49152) throw new ProviderRelayError("Invalid provider body chunk.");
1421
+ pending.received += bytes.byteLength;
1422
+ if (pending.received > 16 * 1024 * 1024 || pending.buffered + bytes.byteLength > 2 * 1024 * 1024) {
1423
+ this.#finish(state, pending, new Error("Provider response exceeded relay limits."));
1424
+ throw new ProviderRelayError("Provider response exceeded relay limits.", 413);
1425
+ }
1426
+ if ((pending.controller?.desiredSize ?? 0) > 0 && pending.queue.length === 0) pending.controller.enqueue(bytes);
1427
+ else {
1428
+ pending.queue.push(bytes);
1429
+ pending.buffered += bytes.byteLength;
1430
+ }
1431
+ } else this.#finish(state, pending, event.kind === "error" ? new Error("Local model source disconnected or failed.") : void 0);
1432
+ }
1433
+ return { accepted: true };
1434
+ }
1435
+ close(id2) {
1436
+ const state = this.#grants.get(id2);
1437
+ if (!state) return;
1438
+ for (const pending of [...state.requests.values()]) this.#finish(state, pending, new Error("Local model source disconnected."));
1439
+ this.#grants.delete(id2);
1440
+ }
1441
+ dispose() {
1442
+ for (const id2 of [...this.#grants.keys()]) this.close(id2);
1443
+ this.#disposed = true;
1444
+ }
1445
+ };
1446
+ async function handleRemoteProviderRelayRequest(req, res, url, broker, readBody) {
1447
+ const base = "/api/console/provider-relays";
1448
+ if (url.pathname !== base && !url.pathname.startsWith(`${base}/`)) return false;
1449
+ const send = (status, value) => {
1450
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
1451
+ res.end(JSON.stringify(value));
1452
+ };
1453
+ try {
1454
+ if (url.pathname === base && req.method === "POST") {
1455
+ send(201, { grant: broker.create(await readBody()) });
1456
+ return true;
1457
+ }
1458
+ const status = /^\/api\/console\/provider-relays\/([a-f0-9-]{36})\/status$/.exec(url.pathname);
1459
+ if (status) {
1460
+ if (req.method !== "GET") throw new ProviderRelayError("Method not allowed.", 405);
1461
+ send(200, broker.status(status[1]));
1462
+ return true;
1463
+ }
1464
+ const match = /^\/api\/console\/provider-relays\/([a-f0-9-]{36})(?:\/requests(?:\/([a-f0-9-]{36})\/events)?)?$/.exec(url.pathname);
1465
+ if (!match) throw new ProviderRelayError("Unknown provider relay route.", 404);
1466
+ if (req.method === "DELETE" && !url.pathname.endsWith("/requests") && !match[2]) {
1467
+ broker.close(match[1]);
1468
+ send(200, { closed: true });
1469
+ } else if (req.method === "GET" && url.pathname.endsWith("/requests")) send(200, broker.poll(match[1]));
1470
+ else if (req.method === "POST" && match[2]) send(200, broker.events(match[1], match[2], await readBody()));
1471
+ else throw new ProviderRelayError("Method not allowed.", 405);
1472
+ } catch (error2) {
1473
+ send(error2 instanceof ProviderRelayError ? error2.statusCode : 400, { error: error2 instanceof ProviderRelayError ? error2.message : "Invalid provider relay request." });
1474
+ }
1475
+ return true;
1476
+ }
1477
+
659
1478
  // packages/cli/src/web/report-artifacts.ts
660
1479
  var ReportArtifactError = class extends Error {
661
1480
  constructor(message2, statusCode) {
@@ -711,13 +1530,13 @@ function retainedFindingSnapshot(ids, dbPath) {
711
1530
  try {
712
1531
  const findings = [];
713
1532
  const scanIds = /* @__PURE__ */ new Set();
714
- for (const id of new Set(ids)) {
715
- const row = db.getFinding(id);
1533
+ for (const id2 of new Set(ids)) {
1534
+ const row = db.getFinding(id2);
716
1535
  if (!row) throw new ReportArtifactError("A selected finding was not found in this engine.", 404);
717
1536
  scanIds.add(row.scanId);
718
- findings.push(loadFindingFocus(id, { dbPath }).finding);
1537
+ findings.push(loadFindingFocus(id2, { dbPath }).finding);
719
1538
  }
720
- const scans = [...scanIds].map((id) => db.getScan(id)).filter((scan) => scan !== void 0);
1539
+ const scans = [...scanIds].map((id2) => db.getScan(id2)).filter((scan) => scan !== void 0);
721
1540
  return {
722
1541
  target: scans.length === 1 ? scans[0].target : "Selected findings from multiple runs",
723
1542
  scanDepth: "default",
@@ -763,7 +1582,7 @@ function handleFindingImpactRequest(pathname, method, input, options) {
763
1582
  }
764
1583
 
765
1584
  // packages/cli/src/web/findings-access-tokens.ts
766
- import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
1585
+ import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
767
1586
  import { closeSync, constants, fstatSync, mkdirSync, openSync, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync } from "node:fs";
768
1587
  import { join } from "node:path";
769
1588
 
@@ -1442,13 +2261,13 @@ function createRemoteJWKSet(url, options) {
1442
2261
  }
1443
2262
  const reload = async () => {
1444
2263
  if (pendingFetch && isCloudflareWorkers() && (pendingFetch = void 0), !pendingFetch) {
1445
- const sequence = ++reloadSequence, current = pendingFetch = fetchJwks(href, headers, AbortSignal.timeout(timeoutDuration), fetchImpl).then((json2) => {
1446
- const next = createLocalJWKSet(json2);
2264
+ const sequence = ++reloadSequence, current = pendingFetch = fetchJwks(href, headers, AbortSignal.timeout(timeoutDuration), fetchImpl).then((json3) => {
2265
+ const next = createLocalJWKSet(json3);
1447
2266
  if (sequence <= appliedSequence)
1448
2267
  return;
1449
2268
  local = next;
1450
2269
  const updatedAt = Date.now();
1451
- cache2 && (cache2.uat = updatedAt, cache2.jwks = json2), jwksTimestamp = updatedAt, appliedSequence = sequence;
2270
+ cache2 && (cache2.uat = updatedAt, cache2.jwks = json3), jwksTimestamp = updatedAt, appliedSequence = sequence;
1452
2271
  }).finally(() => {
1453
2272
  pendingFetch === current && (pendingFetch = void 0);
1454
2273
  });
@@ -1534,8 +2353,8 @@ function parseCookies(header) {
1534
2353
  return cookies;
1535
2354
  }
1536
2355
  function getSessionId(req) {
1537
- const id = parseCookies(req.headers.cookie)[COOKIE];
1538
- return id && /^[\w-]{43}$/.test(id) ? id : void 0;
2356
+ const id2 = parseCookies(req.headers.cookie)[COOKIE];
2357
+ return id2 && /^[\w-]{43}$/.test(id2) ? id2 : void 0;
1539
2358
  }
1540
2359
  async function hashTeamPassword(password) {
1541
2360
  if (!password || password.length > 1024) throw new TeamAuthError("Password must contain 1\u20131024 characters", 400);
@@ -1592,8 +2411,8 @@ var TeamAuth = class {
1592
2411
  cookie(name, value, seconds) {
1593
2412
  return `${name}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${seconds}${this.origin.startsWith("https:") ? "; Secure" : ""}`;
1594
2413
  }
1595
- lookupUser(id) {
1596
- const user = this.config?.users.find((row) => row.id === id);
2414
+ lookupUser(id2) {
2415
+ const user = this.config?.users.find((row) => row.id === id2);
1597
2416
  return user ? { workspaceId: this.config.workspace.id, userId: user.id, displayName: user.name, role: user.role } : null;
1598
2417
  }
1599
2418
  /** Trusted server configuration only; never serialize this through a browser API. */
@@ -1617,11 +2436,11 @@ var TeamAuth = class {
1617
2436
  this.config.users.push(member);
1618
2437
  return this.lookupUser(member.id);
1619
2438
  }
1620
- resolveSessionId(id) {
1621
- if (!id) return null;
1622
- const session = this.sessions.get(id);
2439
+ resolveSessionId(id2) {
2440
+ if (!id2) return null;
2441
+ const session = this.sessions.get(id2);
1623
2442
  if (!session || session.expires <= this.now()) {
1624
- this.sessions.delete(id);
2443
+ this.sessions.delete(id2);
1625
2444
  return null;
1626
2445
  }
1627
2446
  return this.lookupUser(session.userId);
@@ -1633,7 +2452,7 @@ var TeamAuth = class {
1633
2452
  return { enabled: this.enabled, ...this.config ? { workspace: this.config.workspace } : {}, user: this.resolveSession(req), sso: !!this.config?.oidc };
1634
2453
  }
1635
2454
  issueSession(userId) {
1636
- for (const [id, session] of this.sessions) if (session.expires <= this.now()) this.sessions.delete(id);
2455
+ for (const [id2, session] of this.sessions) if (session.expires <= this.now()) this.sessions.delete(id2);
1637
2456
  if (this.sessions.size >= 1e4) throw new TeamAuthError("Session capacity reached", 503);
1638
2457
  const sessionId = opaque();
1639
2458
  this.sessions.set(sessionId, { userId, expires: this.now() + SESSION_MS });
@@ -1644,7 +2463,7 @@ var TeamAuth = class {
1644
2463
  const parsed = external_exports.object({ userId: external_exports.string().min(1).max(160), password: external_exports.string().min(1).max(1024) }).strict().safeParse(input);
1645
2464
  if (!parsed.success) throw new TeamAuthError("User ID and password are required", 400);
1646
2465
  const key = rateKey;
1647
- for (const [id, row] of this.attempts) if (row.expires <= this.now()) this.attempts.delete(id);
2466
+ for (const [id2, row] of this.attempts) if (row.expires <= this.now()) this.attempts.delete(id2);
1648
2467
  const attempt = this.attempts.get(key) ?? { count: 0, expires: this.now() + 6e4 };
1649
2468
  if (attempt.count >= 10 || this.attempts.size >= 1e4) throw new TeamAuthError("Too many sign-in attempts; try again shortly", 429);
1650
2469
  attempt.count++;
@@ -1655,8 +2474,8 @@ var TeamAuth = class {
1655
2474
  return this.issueSession(user.id);
1656
2475
  }
1657
2476
  logout(req) {
1658
- const id = getSessionId(req);
1659
- if (id) this.sessions.delete(id);
2477
+ const id2 = getSessionId(req);
2478
+ if (id2) this.sessions.delete(id2);
1660
2479
  return { setCookie: this.cookie(COOKIE, "", 0) };
1661
2480
  }
1662
2481
  requireProviderURL(value) {
@@ -1680,7 +2499,7 @@ var TeamAuth = class {
1680
2499
  async beginSSO() {
1681
2500
  const discovery = await this.getDiscovery();
1682
2501
  const oidc = this.config.oidc;
1683
- for (const [id, pending] of this.pending) if (pending.expires <= this.now()) this.pending.delete(id);
2502
+ for (const [id2, pending] of this.pending) if (pending.expires <= this.now()) this.pending.delete(id2);
1684
2503
  if (this.pending.size >= 1e3) throw new TeamAuthError("Too many pending sign-ins", 429);
1685
2504
  const state = opaque(), nonce = opaque(), verifier = opaque(), browser = opaque();
1686
2505
  this.pending.set(state, { nonce, verifier, browser, expires: this.now() + SSO_MS });
@@ -1739,7 +2558,7 @@ var publicTokenSchema = external_exports.object({
1739
2558
  }).strict();
1740
2559
  var tokenSchema = publicTokenSchema.extend({ tokenHash: external_exports.string().regex(/^[a-f0-9]{64}$/) }).strict();
1741
2560
  var stateSchema = external_exports.object({ schemaVersion: external_exports.literal(1), workspaceId: external_exports.string(), tokens: external_exports.array(tokenSchema).max(1e3) }).strict();
1742
- var createSchema = external_exports.object({
2561
+ var createSchema2 = external_exports.object({
1743
2562
  name: external_exports.string().trim().min(1).max(160),
1744
2563
  scopes: scopesSchema.optional(),
1745
2564
  expiresAt: external_exports.string().datetime().nullable().optional()
@@ -1788,7 +2607,7 @@ var FindingsAccessTokenStore = class {
1788
2607
  #write(records) {
1789
2608
  const state = stateSchema.safeParse({ schemaVersion: 1, workspaceId: this.workspaceId, tokens: records });
1790
2609
  if (!state.success) throw new FindingsAccessError("API credential limit reached.", 409);
1791
- const temporary = this.#path + "." + randomUUID2() + ".tmp";
2610
+ const temporary = this.#path + "." + randomUUID3() + ".tmp";
1792
2611
  try {
1793
2612
  writeFileSync(temporary, JSON.stringify(state.data), { flag: "wx", mode: 384 });
1794
2613
  renameSync(temporary, this.#path);
@@ -1822,22 +2641,22 @@ var FindingsAccessTokenStore = class {
1822
2641
  /** Caller supplies a verified owner identity, never createdBy from the HTTP body. */
1823
2642
  create(input, createdBy) {
1824
2643
  return this.#mutate(() => {
1825
- const parsed = createSchema.safeParse(input);
2644
+ const parsed = createSchema2.safeParse(input);
1826
2645
  if (!parsed.success || !createdBy || createdBy.length > 256) throw new FindingsAccessError("Use a name, optional read:findings scope and future expiry.");
1827
2646
  const records = this.#read();
1828
2647
  const now = this.#now();
1829
2648
  const expiresAt = parsed.data.expiresAt === void 0 ? new Date(now + 90 * 864e5).toISOString() : parsed.data.expiresAt;
1830
2649
  if (expiresAt !== null && Date.parse(expiresAt) <= now) throw new FindingsAccessError("API credential expiry must be in the future.");
1831
2650
  const token = "0_find_" + randomBytes2(32).toString("base64url");
1832
- const record = { id: randomUUID2(), workspaceId: this.workspaceId, name: parsed.data.name, scopes: ["read:findings"], createdBy, createdAt: new Date(now).toISOString(), expiresAt, revokedAt: null, tokenHash: createHash2("sha256").update(token).digest("hex") };
2651
+ const record = { id: randomUUID3(), workspaceId: this.workspaceId, name: parsed.data.name, scopes: ["read:findings"], createdBy, createdAt: new Date(now).toISOString(), expiresAt, revokedAt: null, tokenHash: createHash2("sha256").update(token).digest("hex") };
1833
2652
  this.#write([...records, record]);
1834
2653
  return { credential: redact(record), token };
1835
2654
  });
1836
2655
  }
1837
- revoke(id) {
2656
+ revoke(id2) {
1838
2657
  return this.#mutate(() => {
1839
2658
  const records = this.#read();
1840
- const record = records.find((token) => token.id === id);
2659
+ const record = records.find((token) => token.id === id2);
1841
2660
  if (!record) throw new FindingsAccessError("API credential not found.", 404);
1842
2661
  record.revokedAt ??= new Date(this.#now()).toISOString();
1843
2662
  this.#write(records);
@@ -1894,12 +2713,12 @@ var FindingsReadApi = class {
1894
2713
  this.#dbPath = resolveOsecDbPath(options.dbPath);
1895
2714
  this.workspaceId = options.workspaceId;
1896
2715
  }
1897
- #item(db, id) {
1898
- const row = db.getFinding(id);
2716
+ #item(db, id2) {
2717
+ const row = db.getFinding(id2);
1899
2718
  if (!row) throw new FindingsAccessError("Finding not found.", 404);
1900
2719
  const scan = db.getScan(row.scanId);
1901
2720
  if (!scan) throw new FindingsAccessError("Finding source scan was not retained.", 404);
1902
- return { scanId: row.scanId, target: scan.target, finding: findingFromRow(row, { ...db.getFindingReviewFields(id) }) };
2721
+ return { scanId: row.scanId, target: scan.target, finding: findingFromRow(row, { ...db.getFindingReviewFields(id2) }) };
1903
2722
  }
1904
2723
  list(query) {
1905
2724
  const raw = {};
@@ -1934,11 +2753,11 @@ var FindingsReadApi = class {
1934
2753
  db.close();
1935
2754
  }
1936
2755
  }
1937
- detail(id) {
1938
- if (!boundedId.safeParse(id).success) throw new FindingsAccessError("Invalid finding ID.");
2756
+ detail(id2) {
2757
+ if (!boundedId.safeParse(id2).success) throw new FindingsAccessError("Invalid finding ID.");
1939
2758
  const db = new osecDB(this.#dbPath, { readOnly: true });
1940
2759
  try {
1941
- return { schemaVersion: 1, workspaceId: this.workspaceId, ...this.#item(db, id) };
2760
+ return { schemaVersion: 1, workspaceId: this.workspaceId, ...this.#item(db, id2) };
1942
2761
  } finally {
1943
2762
  db.close();
1944
2763
  }
@@ -1946,7 +2765,7 @@ var FindingsReadApi = class {
1946
2765
  export(query) {
1947
2766
  if ([...query.keys()].some((key) => key !== "id")) throw new FindingsAccessError("Use repeated id parameters to select export findings.");
1948
2767
  const ids = query.getAll("id");
1949
- if (!ids.length || ids.length > 500 || ids.some((id) => !boundedId.safeParse(id).success) || new Set(ids).size !== ids.length) throw new FindingsAccessError("Select 1\u2013500 distinct exact finding IDs.");
2768
+ if (!ids.length || ids.length > 500 || ids.some((id2) => !boundedId.safeParse(id2).success) || new Set(ids).size !== ids.length) throw new FindingsAccessError("Select 1\u2013500 distinct exact finding IDs.");
1950
2769
  return { schemaVersion: 1, workspaceId: this.workspaceId, coverage: "selected-retained-findings", report: retainedFindingSnapshot(ids, this.#dbPath) };
1951
2770
  }
1952
2771
  };
@@ -2023,8 +2842,11 @@ function engagementMarkdown(report) {
2023
2842
  return lines.filter((line, index) => line !== "" || lines[index - 1] !== "").join("\n");
2024
2843
  }
2025
2844
 
2845
+ // packages/cli/src/web/workspace-services.ts
2846
+ import { randomUUID as randomUUID6 } from "node:crypto";
2847
+
2026
2848
  // packages/cli/src/web/skills.ts
2027
- import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
2849
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
2028
2850
  import { constants as constants2, closeSync as closeSync2, fstatSync as fstatSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, readdirSync, realpathSync as realpathSync2, renameSync as renameSync2, rmSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
2029
2851
  import { join as join2, resolve as resolve2 } from "node:path";
2030
2852
  import { homedir as homedir2 } from "node:os";
@@ -2121,12 +2943,12 @@ var SkillsStore = class _SkillsStore {
2121
2943
  #discovery() {
2122
2944
  return discoverAgentSkills(this.discoveryOptions());
2123
2945
  }
2124
- #builtin(id) {
2125
- const builtinId = id.startsWith("builtin/") ? id.slice(8) : id.includes("/") ? void 0 : id;
2946
+ #builtin(id2) {
2947
+ const builtinId = id2.startsWith("builtin/") ? id2.slice(8) : id2.includes("/") ? void 0 : id2;
2126
2948
  if (!builtinId) return;
2127
2949
  const skill = loadSkillRegistry().get(builtinId);
2128
2950
  if (!skill) return;
2129
- id = `builtin/${builtinId}`;
2951
+ id2 = `builtin/${builtinId}`;
2130
2952
  const name = skill.id;
2131
2953
  const content = `---
2132
2954
  name: ${name}
@@ -2136,20 +2958,20 @@ description: ${JSON.stringify(skill.description)}
2136
2958
  ${skill.content}
2137
2959
  `;
2138
2960
  const files = [{ path: "SKILL.md", bytes: Buffer.from(content) }];
2139
- return { metadata: { id, name, description: skill.description, scope: "builtin", revision: revision(files), writable: false, fileCount: 1, source: "Built in" }, files };
2961
+ return { metadata: { id: id2, name, description: skill.description, scope: "builtin", revision: revision(files), writable: false, fileCount: 1, source: "Built in" }, files };
2140
2962
  }
2141
- #entry(id) {
2142
- const built = this.#builtin(id);
2963
+ #entry(id2) {
2964
+ const built = this.#builtin(id2);
2143
2965
  if (built) return built;
2144
- const skill = this.#discovery().skills.find((row) => row.id === id);
2966
+ const skill = this.#discovery().skills.find((row) => row.id === id2);
2145
2967
  if (!skill) throw new SkillsError("Skill not found.", 404);
2146
2968
  return this.#describe(skill);
2147
2969
  }
2148
2970
  #describe(skill) {
2149
- const id = skill.id;
2971
+ const id2 = skill.id;
2150
2972
  const files = readFiles(skill.directory);
2151
2973
  const shared = skill.root === this.#shared || skill.directory.startsWith(this.#shared + "/");
2152
- return { metadata: { id, name: skill.name, description: skill.description, scope: shared ? "workspace" : skill.source === "mount" ? "mounted" : skill.source, revision: revision(files), writable: shared, fileCount: files.length, source: shared ? "Workspace" : skill.directory }, files, directory: skill.directory };
2974
+ return { metadata: { id: id2, name: skill.name, description: skill.description, scope: shared ? "workspace" : skill.source === "mount" ? "mounted" : skill.source, revision: revision(files), writable: shared, fileCount: files.length, source: shared ? "Workspace" : skill.directory }, files, directory: skill.directory };
2153
2975
  }
2154
2976
  #access(metadata, access = {}) {
2155
2977
  return { ...metadata, writable: access.canWrite !== false && metadata.scope !== "builtin" && (metadata.scope === "workspace" || access.allowHostEdits === true) };
@@ -2168,12 +2990,12 @@ ${skill.content}
2168
2990
  for (const skill of loadSkillRegistry().values()) skills.push(this.#builtin(`builtin/${skill.id}`).metadata);
2169
2991
  return { skills, mounts: this.mounts(), diagnostics, workspacePath: this.workspacePath };
2170
2992
  }
2171
- get(id, access = {}) {
2172
- const row = this.#entry(id);
2993
+ get(id2, access = {}) {
2994
+ const row = this.#entry(id2);
2173
2995
  return { ...this.#access(row.metadata, access), content: row.files.find((file) => file.path === "SKILL.md").bytes.toString("utf8"), files: row.files.map((file) => ({ path: file.path, size: file.bytes.length })) };
2174
2996
  }
2175
- export(id) {
2176
- const row = this.#entry(id);
2997
+ export(id2) {
2998
+ const row = this.#entry(id2);
2177
2999
  return { format: "agent-skill", version: 1, name: row.metadata.name, files: row.files.map((file) => ({ path: file.path, encoding: "base64", content: file.bytes.toString("base64") })) };
2178
3000
  }
2179
3001
  #validate(content, name) {
@@ -2241,7 +3063,7 @@ ${skill.content}
2241
3063
  } catch (cause) {
2242
3064
  if (cause.code !== "ENOENT") throw cause;
2243
3065
  }
2244
- const temporary = join2(this.#state, `.import-${randomUUID3()}`);
3066
+ const temporary = join2(this.#state, `.import-${randomUUID4()}`);
2245
3067
  mkdirSync2(temporary, { mode: 448 });
2246
3068
  try {
2247
3069
  for (const file of files) {
@@ -2280,31 +3102,31 @@ ${skill.content}
2280
3102
  if (!names.has("SKILL.md")) throw new SkillsError("Skill bundle needs SKILL.md.");
2281
3103
  return this.get(this.#write(files, bundle.name));
2282
3104
  }
2283
- update(id, input, access = {}) {
2284
- return this.#transaction(() => this.#update(id, input, access));
3105
+ update(id2, input, access = {}) {
3106
+ return this.#transaction(() => this.#update(id2, input, access));
2285
3107
  }
2286
- #update(id, input, access) {
3108
+ #update(id2, input, access) {
2287
3109
  const { content, expectedRevision } = parse(external_exports.object({ content: external_exports.string().min(1).max(MAX_BYTES), expectedRevision: external_exports.string().regex(/^[a-f0-9]{64}$/) }).strict(), input);
2288
- const row = this.#entry(id);
3110
+ const row = this.#entry(id2);
2289
3111
  if (!this.#access(row.metadata, access).writable) throw new SkillsError(row.metadata.scope === "builtin" ? "Save a workspace copy to edit this built-in skill." : "This account cannot edit this skill folder. Save a workspace copy instead.", 403);
2290
3112
  if (row.metadata.revision !== expectedRevision) throw new SkillsError("This skill changed. Reload it before saving.", 409);
2291
3113
  this.#validate(content, row.metadata.name);
2292
3114
  if (row.files.reduce((total, file) => total + (file.path === "SKILL.md" ? Buffer.byteLength(content) : file.bytes.length), 0) > MAX_BYTES) throw new SkillsError("Skill bundle exceeds its size limit.");
2293
3115
  const target = join2(row.directory, "SKILL.md");
2294
- const pending = join2(row.directory, `.skill-edit-${randomUUID3()}.tmp`);
3116
+ const pending = join2(row.directory, `.skill-edit-${randomUUID4()}.tmp`);
2295
3117
  try {
2296
3118
  writeFileSync2(pending, content, { flag: "wx", mode: lstatSync2(target).mode & 511 });
2297
3119
  renameSync2(pending, target);
2298
3120
  } finally {
2299
3121
  rmSync(pending, { force: true });
2300
3122
  }
2301
- return this.get(id, access);
3123
+ return this.get(id2, access);
2302
3124
  }
2303
- copy(id, input, access = {}) {
3125
+ copy(id2, input, access = {}) {
2304
3126
  if (access.canWrite === false) throw new SkillsError("This account has read-only workspace access.", 403);
2305
3127
  return this.#transaction(() => {
2306
3128
  const { content, expectedRevision } = parse(external_exports.object({ content: external_exports.string().min(1).max(MAX_BYTES), expectedRevision: external_exports.string().regex(/^[a-f0-9]{64}$/) }).strict(), input);
2307
- const row = this.#entry(id);
3129
+ const row = this.#entry(id2);
2308
3130
  if (row.metadata.revision !== expectedRevision) throw new SkillsError("This skill changed. Reload it before copying.", 409);
2309
3131
  const { name } = this.#validate(content);
2310
3132
  const files = row.files.map((file) => file.path === "SKILL.md" ? { path: file.path, bytes: Buffer.from(content) } : file);
@@ -2319,18 +3141,18 @@ ${skill.content}
2319
3141
  };
2320
3142
  return {
2321
3143
  list: () => this.list(access),
2322
- read: (id) => this.get(id, access),
3144
+ read: (id2) => this.get(id2, access),
2323
3145
  create: (content) => {
2324
3146
  assertWrite();
2325
3147
  return this.create({ content });
2326
3148
  },
2327
- update: (id, input) => {
3149
+ update: (id2, input) => {
2328
3150
  assertWrite();
2329
- return this.update(id, input, access);
3151
+ return this.update(id2, input, access);
2330
3152
  },
2331
- copy: (id, input) => {
3153
+ copy: (id2, input) => {
2332
3154
  assertWrite();
2333
- return this.copy(id, input, access);
3155
+ return this.copy(id2, input, access);
2334
3156
  }
2335
3157
  };
2336
3158
  }
@@ -2349,24 +3171,24 @@ ${skill.content}
2349
3171
  const mounts = this.mounts();
2350
3172
  if (mounts.length >= 100) throw new SkillsError("At most 100 folders can be mounted.", 409);
2351
3173
  if (mounts.some((item) => item.path === canonical)) throw new SkillsError("This folder is already mounted.", 409);
2352
- const mount = { id: randomUUID3(), path: canonical };
3174
+ const mount = { id: randomUUID4(), path: canonical };
2353
3175
  const found = discoverAgentSkills({ homeDir: null, mounts: [mount] });
2354
3176
  if (!found.skills.length) throw new SkillsError("No valid SKILL.md folders found.");
2355
3177
  mounts.push(mount);
2356
3178
  this.#saveMounts(mounts);
2357
3179
  return mount;
2358
3180
  }
2359
- unmount(id) {
2360
- return this.#transaction(() => this.#unmount(id));
3181
+ unmount(id2) {
3182
+ return this.#transaction(() => this.#unmount(id2));
2361
3183
  }
2362
- #unmount(id) {
3184
+ #unmount(id2) {
2363
3185
  const mounts = this.mounts();
2364
- if (!mounts.some((item) => item.id === id)) throw new SkillsError("Mount not found.", 404);
2365
- this.#saveMounts(mounts.filter((item) => item.id !== id));
3186
+ if (!mounts.some((item) => item.id === id2)) throw new SkillsError("Mount not found.", 404);
3187
+ this.#saveMounts(mounts.filter((item) => item.id !== id2));
2366
3188
  }
2367
3189
  #saveMounts(mounts) {
2368
3190
  mkdirSync2(this.#state, { recursive: true, mode: 448 });
2369
- const temporary = join2(this.#state, `.mounts-${randomUUID3()}`);
3191
+ const temporary = join2(this.#state, `.mounts-${randomUUID4()}`);
2370
3192
  try {
2371
3193
  writeFileSync2(temporary, JSON.stringify(mounts), { flag: "wx", mode: 384 });
2372
3194
  renameSync2(temporary, join2(this.#state, "mounts.json"));
@@ -2377,7 +3199,7 @@ ${skill.content}
2377
3199
  };
2378
3200
 
2379
3201
  // packages/cli/src/web/engagements.ts
2380
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
3202
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
2381
3203
  import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync3, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync4, realpathSync as realpathSync3, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
2382
3204
  import { join as join3 } from "node:path";
2383
3205
  var EngagementError = class extends Error {
@@ -2438,7 +3260,7 @@ var EngagementStore = class {
2438
3260
  const body = JSON.stringify({ schemaVersion: 1, workspace: this.workspace, engagements });
2439
3261
  if (Buffer.byteLength(body) > MAX_STATE_BYTES) throw new EngagementError("Engagement storage limit reached.", 409);
2440
3262
  mkdirSync3(this.#directory, { recursive: true, mode: 448 });
2441
- const temporary = `${this.#path}.${randomUUID4()}.tmp`;
3263
+ const temporary = `${this.#path}.${randomUUID5()}.tmp`;
2442
3264
  try {
2443
3265
  writeFileSync3(temporary, body, { flag: "wx", mode: 384 });
2444
3266
  renameSync3(temporary, this.#path);
@@ -2454,7 +3276,7 @@ var EngagementStore = class {
2454
3276
  if (!ids.length) return;
2455
3277
  const db = new osecDB(this.#dbPath);
2456
3278
  try {
2457
- for (const id of ids) if (!db.getScan(id)) throw new EngagementError(`Selected scan was not found: ${id}`, 404);
3279
+ for (const id2 of ids) if (!db.getScan(id2)) throw new EngagementError(`Selected scan was not found: ${id2}`, 404);
2458
3280
  } finally {
2459
3281
  db.close();
2460
3282
  }
@@ -2514,8 +3336,8 @@ var EngagementStore = class {
2514
3336
  list() {
2515
3337
  return this.#read().sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
2516
3338
  }
2517
- get(id) {
2518
- const row = this.#read().find((item) => item.id === id);
3339
+ get(id2) {
3340
+ const row = this.#read().find((item) => item.id === id2);
2519
3341
  if (!row) throw new EngagementError("Engagement not found in this workspace.", 404);
2520
3342
  return row;
2521
3343
  }
@@ -2526,17 +3348,17 @@ var EngagementStore = class {
2526
3348
  return this.#transaction((rows) => {
2527
3349
  if (rows.length >= 200) throw new EngagementError("Engagement limit reached.", 409);
2528
3350
  const now = (/* @__PURE__ */ new Date()).toISOString();
2529
- const row = { ...fields, id: randomUUID4(), revision: 1, createdAt: now, updatedAt: now, ...attribution ? { createdBy: attribution, updatedBy: attribution } : {} };
3351
+ const row = { ...fields, id: randomUUID5(), revision: 1, createdAt: now, updatedAt: now, ...attribution ? { createdBy: attribution, updatedBy: attribution } : {} };
2530
3352
  rows.push(row);
2531
3353
  return structuredClone(row);
2532
3354
  });
2533
3355
  }
2534
- update(id, input, actor) {
3356
+ update(id2, input, actor) {
2535
3357
  const { expectedRevision, ...patch } = parse2(updateSchema, input);
2536
3358
  const attribution = actor ? parse2(actorSchema, actor) : void 0;
2537
3359
  if (patch.scanIds) this.#validateScans(patch.scanIds);
2538
3360
  return this.#transaction((rows) => {
2539
- const index = rows.findIndex((row2) => row2.id === id);
3361
+ const index = rows.findIndex((row2) => row2.id === id2);
2540
3362
  if (index < 0) throw new EngagementError("Engagement not found in this workspace.", 404);
2541
3363
  if (attribution && expectedRevision === void 0) throw new EngagementError("Review the latest report before saving. Your draft has been kept.", 409);
2542
3364
  if (expectedRevision !== void 0 && rows[index].revision !== expectedRevision) throw new EngagementError("Report changed. Your draft has been kept. Reload the latest version before saving.", 409);
@@ -2545,8 +3367,8 @@ var EngagementStore = class {
2545
3367
  return structuredClone(row);
2546
3368
  });
2547
3369
  }
2548
- report(id) {
2549
- const engagement = this.get(id);
3370
+ report(id2) {
3371
+ const engagement = this.get(id2);
2550
3372
  const db = new osecDB(this.#dbPath);
2551
3373
  try {
2552
3374
  const sources = engagement.scanIds.map((scanId) => {
@@ -2568,22 +3390,22 @@ function reviewStatus(finding) {
2568
3390
  function assembleEngagementReport(engagement, sources) {
2569
3391
  const record = parse2(recordSchema, engagement);
2570
3392
  const byScan = new Map(sources.map((source) => [source.scan.id, source]));
2571
- if (byScan.size !== sources.length || sources.length !== record.scanIds.length || record.scanIds.some((id) => !byScan.has(id))) throw new EngagementError("Report sources must match exactly the selected scan IDs.", 404);
3393
+ if (byScan.size !== sources.length || sources.length !== record.scanIds.length || record.scanIds.some((id2) => !byScan.has(id2))) throw new EngagementError("Report sources must match exactly the selected scan IDs.", 404);
2572
3394
  const groups = /* @__PURE__ */ new Map();
2573
3395
  let findingCount = 0;
2574
- const scans = record.scanIds.map((id) => {
2575
- const source = byScan.get(id);
3396
+ const scans = record.scanIds.map((id2) => {
3397
+ const source = byScan.get(id2);
2576
3398
  for (const finding of source.report.findings) {
2577
3399
  findingCount++;
2578
3400
  const fingerprint = finding.fingerprint?.trim();
2579
- const key = fingerprint ? `fingerprint:${fingerprint}` : `source:${id}:${finding.id}`;
3401
+ const key = fingerprint ? `fingerprint:${fingerprint}` : `source:${id2}:${finding.id}`;
2580
3402
  const status = reviewStatus(finding);
2581
3403
  let group = groups.get(key);
2582
3404
  if (!group) {
2583
3405
  group = { key, ...fingerprint ? { fingerprint } : {}, reviewStatus: status, occurrences: [] };
2584
3406
  groups.set(key, group);
2585
3407
  } else if (group.reviewStatus !== status) group.reviewStatus = "unreviewed";
2586
- group.occurrences.push({ scanId: id, findingId: finding.id, finding: structuredClone(finding) });
3408
+ group.occurrences.push({ scanId: id2, findingId: finding.id, finding: structuredClone(finding) });
2587
3409
  }
2588
3410
  return { ...source.scan, warnings: structuredClone(source.report.warnings), executionSuccessful: source.report.executionSuccessful, exitReason: source.report.exitReason, ...source.report.error ? { error: source.report.error } : {} };
2589
3411
  });
@@ -2606,8 +3428,8 @@ function assembleEngagementReport(engagement, sources) {
2606
3428
  }
2607
3429
 
2608
3430
  // packages/cli/src/web/workflow-triggers.ts
2609
- var createSchema2 = external_exports.object({ workflowId: external_exports.string().min(1).max(160), workflowRevision: external_exports.number().int().positive(), sessionId: external_exports.string().min(1).max(160), cadence: external_exports.enum(["hourly", "daily", "weekly"]), startAt: external_exports.string().datetime({ offset: true }), timezone: external_exports.string().min(1).max(100), enabled: external_exports.boolean(), approval: external_exports.literal("enable-reviewed-trigger").optional() }).strict();
2610
- var toggleSchema = external_exports.object({ enabled: external_exports.boolean(), approval: external_exports.literal("enable-reviewed-trigger").optional() }).strict();
3431
+ var createSchema3 = external_exports.object({ workflowId: external_exports.string().min(1).max(160), workflowRevision: external_exports.number().int().positive(), sessionId: external_exports.string().min(1).max(160), cadence: external_exports.enum(["hourly", "daily", "weekly"]), startAt: external_exports.string().datetime({ offset: true }), timezone: external_exports.string().min(1).max(100), enabled: external_exports.boolean(), approval: external_exports.literal("enable-reviewed-trigger").optional() }).strict();
3432
+ var toggleSchema = external_exports.object({ enabled: external_exports.boolean().optional(), cadence: external_exports.enum(["hourly", "daily", "weekly"]).optional(), startAt: external_exports.string().datetime({ offset: true }).optional(), timezone: external_exports.string().min(1).max(100).optional(), workflowRevision: external_exports.number().int().positive().optional(), expectedVersion: external_exports.number().int().positive().optional(), approval: external_exports.literal("enable-reviewed-trigger").optional() }).strict().refine((value) => ["enabled", "cadence", "startAt", "timezone", "workflowRevision"].some((key) => key in value), "Provide a schedule change.");
2611
3433
  var PREFIX = "/api/console/workflow-triggers";
2612
3434
  var terminal = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted"]);
2613
3435
  var WorkflowTriggerService = class {
@@ -2632,10 +3454,10 @@ var WorkflowTriggerService = class {
2632
3454
  async handle(method, url, input) {
2633
3455
  if (url.pathname !== PREFIX && !url.pathname.startsWith(`${PREFIX}/`)) return null;
2634
3456
  try {
2635
- const id = url.pathname.slice(PREFIX.length + 1);
3457
+ const id2 = url.pathname.slice(PREFIX.length + 1);
2636
3458
  if (url.pathname === PREFIX && method === "GET") return { status: 200, data: { triggers: this.#store.list(url.searchParams.get("workflowId") ?? void 0).map(publicTrigger) } };
2637
3459
  if (url.pathname === PREFIX && method === "POST") {
2638
- const parsed = createSchema2.parse(input);
3460
+ const parsed = createSchema3.parse(input);
2639
3461
  try {
2640
3462
  new Intl.DateTimeFormat("en", { timeZone: parsed.timezone });
2641
3463
  } catch {
@@ -2654,22 +3476,42 @@ var WorkflowTriggerService = class {
2654
3476
  }
2655
3477
  return { status: 201, data: { trigger: publicTrigger(this.#store.create({ ...fields, reviewedConnectionIdentity: identity?.connectionIdentity, reviewedModel: identity?.model, reviewedProviderId: identity?.providerId })) } };
2656
3478
  }
2657
- const trigger = this.#store.get(id);
3479
+ const trigger = this.#store.get(id2);
2658
3480
  if (!trigger) return error(404, "Schedule not found.");
2659
3481
  if (method === "PATCH") {
2660
3482
  const patch = toggleSchema.parse(input);
3483
+ if (patch.expectedVersion !== void 0 && patch.expectedVersion !== (trigger.version ?? 1)) return error(409, "Schedule changed. Review it again before enabling.");
3484
+ if (trigger.activeClaim && ["cadence", "startAt", "timezone", "workflowRevision"].some((key) => key in patch)) return error(409, "Pause the schedule and wait for its active execution before editing.");
3485
+ const changedTiming = patch.cadence !== void 0 || patch.startAt !== void 0 || patch.timezone !== void 0 || patch.workflowRevision !== void 0;
3486
+ const enabled = patch.enabled ?? (changedTiming ? false : trigger.enabled);
3487
+ const next = { ...trigger, ...patch, enabled };
3488
+ try {
3489
+ new Intl.DateTimeFormat("en", { timeZone: next.timezone });
3490
+ } catch {
3491
+ return error(400, "Choose a valid timezone.");
3492
+ }
2661
3493
  let identity = void 0;
2662
- if (patch.enabled) {
3494
+ if (enabled) {
2663
3495
  if (!patch.approval) return error(400, "Review this schedule before enabling it.");
2664
3496
  const definition = this.#workflows.get(trigger.workflowId);
2665
- if (definition?.revision !== trigger.workflowRevision) return error(409, "Workflow changed. Create a schedule for its current revision.");
2666
- identity = await this.#adapter.validate(trigger);
2667
- this.#verifyIdentity(trigger, identity);
3497
+ if (definition?.revision !== next.workflowRevision) return error(409, "Workflow changed. Review its current revision before scheduling.");
3498
+ identity = await this.#adapter.validate(next);
3499
+ if (!changedTiming) this.#verifyIdentity(trigger, identity);
2668
3500
  }
2669
- return { status: 200, data: { trigger: publicTrigger(this.#store.update(id, { ...patch.enabled ? { reviewedModel: trigger.reviewedModel ?? identity?.model, reviewedProviderId: trigger.reviewedProviderId ?? identity?.providerId, reviewedConnectionIdentity: trigger.reviewedConnectionIdentity ?? identity?.connectionIdentity } : {}, enabled: patch.enabled, lastStatus: patch.enabled ? "scheduled" : "paused", lastError: void 0, nextFireAt: nextWorkflowTriggerFire(trigger.startAt, trigger.cadence, Date.now()) }, patch.enabled ? trigger.version ?? 1 : void 0)) } };
3501
+ return { status: 200, data: { trigger: publicTrigger(this.#store.update(id2, {
3502
+ cadence: next.cadence,
3503
+ startAt: next.startAt,
3504
+ timezone: next.timezone,
3505
+ workflowRevision: next.workflowRevision,
3506
+ ...enabled ? { reviewedModel: identity?.model, reviewedProviderId: identity?.providerId, reviewedConnectionIdentity: identity?.connectionIdentity } : {},
3507
+ enabled,
3508
+ lastStatus: enabled ? "scheduled" : "paused",
3509
+ lastError: void 0,
3510
+ nextFireAt: nextWorkflowTriggerFire(next.startAt, next.cadence, Date.now())
3511
+ }, enabled ? trigger.version ?? 1 : patch.expectedVersion)) } };
2670
3512
  }
2671
3513
  if (method === "DELETE") {
2672
- if (!this.#store.delete(id)) return error(409, "Pause the schedule and wait for its active execution before deleting.");
3514
+ if (!this.#store.delete(id2)) return error(409, "Pause the schedule and wait for its active execution before deleting.");
2673
3515
  return { status: 200, data: { deleted: true } };
2674
3516
  }
2675
3517
  return error(405, "Unsupported schedule action.");
@@ -2677,6 +3519,43 @@ var WorkflowTriggerService = class {
2677
3519
  return error(cause instanceof external_exports.ZodError ? 400 : 409, cause instanceof Error ? cause.message.slice(0, 1e3) : "Schedule could not be updated.");
2678
3520
  }
2679
3521
  }
3522
+ /** Chat transport shares the same validation and scheduler as the HTTP editor. */
3523
+ async invokeLifecycle(sessionId, name, args, context) {
3524
+ const identifier = external_exports.string().min(1).max(160);
3525
+ if (name === "list_schedules") {
3526
+ const request = external_exports.object({ workflowId: identifier.optional() }).strict().parse(args);
3527
+ if (context?.workflowId && request.workflowId && context.workflowId !== request.workflowId) throw new Error("This editor is bound to another workflow.");
3528
+ return { schedules: this.#store.list(context?.workflowId ?? request.workflowId).filter((trigger) => context?.workflowId || trigger.sessionId === sessionId).map(publicTrigger) };
3529
+ }
3530
+ let method;
3531
+ let path = PREFIX;
3532
+ let input;
3533
+ if (name === "create_schedule") {
3534
+ const request = createSchema3.omit({ sessionId: true, approval: true }).extend({ enabled: external_exports.boolean().default(false) }).parse(args);
3535
+ if (context?.workflowId && context.workflowId !== request.workflowId) throw new Error("This editor is bound to another workflow.");
3536
+ if (request.enabled && !context?.activationApproved) throw new Error("Schedule activation requires operator approval.");
3537
+ method = "POST";
3538
+ input = { ...request, sessionId, ...request.enabled ? { approval: "enable-reviewed-trigger" } : {} };
3539
+ } else if (name === "update_schedule" || name === "delete_schedule") {
3540
+ const { id: id2, ...fields } = external_exports.object({ id: identifier }).passthrough().parse(args);
3541
+ const trigger = this.#store.get(id2);
3542
+ if (!trigger || (context?.workflowId ? trigger.workflowId !== context.workflowId : trigger.sessionId !== sessionId)) throw new Error("Schedule not found for this editor.");
3543
+ path += `/${encodeURIComponent(id2)}`;
3544
+ if (name === "delete_schedule") {
3545
+ external_exports.object({}).strict().parse(fields);
3546
+ method = "DELETE";
3547
+ input = void 0;
3548
+ } else {
3549
+ const patch = toggleSchema.parse(fields);
3550
+ if (patch.enabled && !context?.activationApproved) throw new Error("Schedule activation requires operator approval.");
3551
+ method = "PATCH";
3552
+ input = { ...patch, ...patch.enabled ? { approval: "enable-reviewed-trigger" } : {} };
3553
+ }
3554
+ } else throw new Error("Unknown schedule action.");
3555
+ const result = await this.handle(method, new URL(path, "http://localhost"), input);
3556
+ if (!result || result.status >= 400) throw new Error(result?.data?.error ?? "Schedule could not be updated.");
3557
+ return result.data;
3558
+ }
2680
3559
  async tick(now = Date.now()) {
2681
3560
  if (this.#closed || this.#polling) return;
2682
3561
  this.#polling = true;
@@ -2852,7 +3731,9 @@ var GitHubPublicationAuth = class {
2852
3731
  // packages/cli/src/web/workspace-services.ts
2853
3732
  async function createWorkspaceServices(options) {
2854
3733
  const skills = new SkillsStore({ workspace: options.workspace ?? process.cwd(), stateDir: options.stateDir ?? homeStateDir(), team: options.auth.enabled });
3734
+ const remoteProviderRelays = new RemoteProviderRelayBroker({ epoch: options.relayEpoch ?? randomUUID6() });
2855
3735
  const gateway = new ConsoleGateway({
3736
+ remoteProviderRelays,
2856
3737
  dbPath: options.dbPath,
2857
3738
  projectPath: options.workspace,
2858
3739
  skillAuthoring: (author, workspaceRoot) => {
@@ -2869,7 +3750,7 @@ async function createWorkspaceServices(options) {
2869
3750
  let triggers;
2870
3751
  let github;
2871
3752
  const dispose = async () => {
2872
- const actions = [() => engine?.dispose(), () => triggers?.dispose(), () => gateway.closeAll(), () => workflows?.dispose(), () => operator.dispose(), () => github?.dispose()];
3753
+ const actions = [() => remoteProviderRelays.dispose(), () => engine?.dispose(), () => triggers?.dispose(), () => gateway.closeAll(), () => workflows?.dispose(), () => operator.dispose(), () => github?.dispose()];
2873
3754
  let failure;
2874
3755
  for (const action of actions) {
2875
3756
  try {
@@ -2900,8 +3781,10 @@ async function createWorkspaceServices(options) {
2900
3781
  return { executionId: result.execution.id };
2901
3782
  }
2902
3783
  } });
3784
+ const schedules = triggers;
3785
+ gateway.attachScheduleLifecycle({ invoke: (sessionId, name, args, context) => schedules.invokeLifecycle(sessionId, name, args, context) });
2903
3786
  github = new GitHubPublicationAuth();
2904
- return { skills, gateway, engagements, operator, workflows: workflow, engine, triggers, github, dispose };
3787
+ return { remoteProviderRelays, skills, gateway, engagements, operator, workflows: workflow, engine, triggers, github, dispose };
2905
3788
  } catch (cause) {
2906
3789
  await dispose().catch(() => {
2907
3790
  });
@@ -2911,7 +3794,7 @@ async function createWorkspaceServices(options) {
2911
3794
 
2912
3795
  // packages/cli/src/web/team-setup.ts
2913
3796
  import { closeSync as closeSync4, constants as constants4, existsSync, fstatSync as fstatSync4, fsyncSync, mkdirSync as mkdirSync4, openSync as openSync4, readFileSync as readFileSync5, realpathSync as realpathSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
2914
- import { createHash as createHash6, randomUUID as randomUUID5 } from "node:crypto";
3797
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
2915
3798
  import { dirname, join as join4, resolve as resolve3 } from "node:path";
2916
3799
  var setupSchema = external_exports.object({
2917
3800
  workspaceName: external_exports.string().trim().min(1).max(160),
@@ -2977,7 +3860,7 @@ var TeamSetupService = class {
2977
3860
  if (auth.members().length >= 1e3) throw new TeamAuthError("Workspace member limit reached", 409);
2978
3861
  this.busy = true;
2979
3862
  const lockPath = `${this.configPath}.lock`;
2980
- const tempPath = `${this.configPath}.${randomUUID5()}.tmp`;
3863
+ const tempPath = `${this.configPath}.${randomUUID7()}.tmp`;
2981
3864
  let lockFD;
2982
3865
  let temporaryFD;
2983
3866
  try {
@@ -3034,7 +3917,7 @@ var TeamSetupService = class {
3034
3917
  try {
3035
3918
  const data = parsed.data;
3036
3919
  const config = {
3037
- workspace: { id: randomUUID5(), name: data.workspaceName },
3920
+ workspace: { id: randomUUID7(), name: data.workspaceName },
3038
3921
  users: [{ id: data.userId, name: data.displayName, role: "owner", passwordHash: await hashTeamPassword(data.password) }]
3039
3922
  };
3040
3923
  const auth = new TeamAuth({ config, origin: this.options.origin() });
@@ -3087,7 +3970,7 @@ async function handleTeamSetupRequest(req, url, setup, readBody) {
3087
3970
  }
3088
3971
 
3089
3972
  // packages/cli/src/web/collaboration.ts
3090
- import { createHash as createHash7, randomUUID as randomUUID6 } from "node:crypto";
3973
+ import { createHash as createHash7, randomUUID as randomUUID8 } from "node:crypto";
3091
3974
  import { closeSync as closeSync5, constants as constants5, fstatSync as fstatSync5, mkdirSync as mkdirSync5, openSync as openSync5, readFileSync as readFileSync6, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync5 } from "node:fs";
3092
3975
  import { join as join5 } from "node:path";
3093
3976
  var CollaborationError = class extends Error {
@@ -3172,9 +4055,9 @@ var CollaborationService = class {
3172
4055
  });
3173
4056
  }
3174
4057
  /** Trusted post-write adapter only: the caller proves this resource exists in its workspace. */
3175
- notifyChanged(kind, id) {
3176
- if (!external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id }).success) throw new CollaborationError("Invalid changed resource.");
3177
- this.#publish({ type: "changed", kind, id });
4058
+ notifyChanged(kind, id2) {
4059
+ if (!external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id: id2 }).success) throw new CollaborationError("Invalid changed resource.");
4060
+ this.#publish({ type: "changed", kind, id: id2 });
3178
4061
  }
3179
4062
  #actor(sessionId) {
3180
4063
  if (typeof sessionId !== "string" || !sessionId || sessionId.length > 4096) throw new CollaborationError("An authenticated team session is required.", 401);
@@ -3201,7 +4084,7 @@ var CollaborationService = class {
3201
4084
  if (bytes.length > 32 * 1024 * 1024) throw new Error("size");
3202
4085
  let source = JSON.parse(bytes.toString("utf8"));
3203
4086
  const legacy = external_exports.object({ schemaVersion: external_exports.literal(1), workspaceId: external_exports.string(), conversations: external_exports.array(external_exports.object({ id: external_exports.string(), revision: external_exports.number(), createdBy: attributionSchema, createdAt: external_exports.number(), members: external_exports.array(memberSchema2) }).passthrough()).max(1e3) }).strict().safeParse(source);
3204
- if (legacy.success) source = { schemaVersion: 2, workspaceId: legacy.data.workspaceId, rooms: legacy.data.conversations.map(({ id, revision: revision2, createdBy, createdAt, members }) => ({ kind: "conversation", id, revision: revision2, createdBy, createdAt, members })) };
4087
+ if (legacy.success) source = { schemaVersion: 2, workspaceId: legacy.data.workspaceId, rooms: legacy.data.conversations.map(({ id: id2, revision: revision2, createdBy, createdAt, members }) => ({ kind: "conversation", id: id2, revision: revision2, createdBy, createdAt, members })) };
3205
4088
  const parsed = stateSchema3.parse(source);
3206
4089
  if (parsed.workspaceId !== this.#options.workspaceId || new Set(parsed.rooms.map((row) => this.#roomKey(row.kind, row.id))).size !== parsed.rooms.length) throw new Error("workspace");
3207
4090
  return parsed.rooms;
@@ -3216,7 +4099,7 @@ var CollaborationService = class {
3216
4099
  if (!state.success) throw new CollaborationError("Collaboration record limit reached.", 409);
3217
4100
  const data = JSON.stringify(state.data);
3218
4101
  if (Buffer.byteLength(data) > 32 * 1024 * 1024) throw new CollaborationError("Collaboration storage limit reached.", 409);
3219
- const temporary = this.#path + "." + randomUUID6() + ".tmp";
4102
+ const temporary = this.#path + "." + randomUUID8() + ".tmp";
3220
4103
  try {
3221
4104
  writeFileSync5(temporary, data, { flag: "wx", mode: 384 });
3222
4105
  renameSync5(temporary, this.#path);
@@ -3278,11 +4161,11 @@ var CollaborationService = class {
3278
4161
  unlinkSync5(this.#lock);
3279
4162
  }
3280
4163
  }
3281
- #roomKey(kind, id) {
3282
- return kind + ":" + id;
4164
+ #roomKey(kind, id2) {
4165
+ return kind + ":" + id2;
3283
4166
  }
3284
- #room(rows, kind, id) {
3285
- const row = rows.find((item) => item.kind === kind && item.id === id);
4167
+ #room(rows, kind, id2) {
4168
+ const row = rows.find((item) => item.kind === kind && item.id === id2);
3286
4169
  if (!row) throw new CollaborationError("Shared resource room not found.", 404);
3287
4170
  return row;
3288
4171
  }
@@ -3304,8 +4187,8 @@ var CollaborationService = class {
3304
4187
  const { workspaceId: _workspace, ...viewer } = actor;
3305
4188
  return viewer;
3306
4189
  }
3307
- #presenceFor(kind, id) {
3308
- const entries = this.#presence.get(this.#roomKey(kind, id));
4190
+ #presenceFor(kind, id2) {
4191
+ const entries = this.#presence.get(this.#roomKey(kind, id2));
3309
4192
  const presence = /* @__PURE__ */ new Map();
3310
4193
  for (const [key, entry] of entries ?? []) {
3311
4194
  if (!entry.viewing || entry.updatedAt + this.#presenceMs <= this.#now()) {
@@ -3332,15 +4215,15 @@ var CollaborationService = class {
3332
4215
  return [...presence.values()];
3333
4216
  }
3334
4217
  /** Routing must validate that the underlying resource exists before registration. */
3335
- registerRoom(kind, id, sessionId) {
4218
+ registerRoom(kind, id2, sessionId) {
3336
4219
  const actor = this.#actor(sessionId);
3337
- const valid = external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id });
4220
+ const valid = external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id: id2 });
3338
4221
  if (!valid.success) throw new CollaborationError("Invalid resource room.");
3339
4222
  let joined = false;
3340
4223
  this.#transaction((rows) => {
3341
- let row = rows.find((item) => item.kind === kind && item.id === id);
4224
+ let row = rows.find((item) => item.kind === kind && item.id === id2);
3342
4225
  if (!row) {
3343
- row = { kind, id, revision: 0, createdBy: this.#attribution(actor), createdAt: this.#now(), members: [] };
4226
+ row = { kind, id: id2, revision: 0, createdBy: this.#attribution(actor), createdAt: this.#now(), members: [] };
3344
4227
  rows.push(row);
3345
4228
  }
3346
4229
  const revision2 = row.revision;
@@ -3348,7 +4231,7 @@ var CollaborationService = class {
3348
4231
  joined = row.revision !== revision2;
3349
4232
  });
3350
4233
  if (joined) this.#notifyPresence();
3351
- return this.roomSnapshot(kind, id, sessionId);
4234
+ return this.roomSnapshot(kind, id2, sessionId);
3352
4235
  }
3353
4236
  register(conversationId, sessionId) {
3354
4237
  this.registerRoom("conversation", conversationId, sessionId);
@@ -3357,10 +4240,10 @@ var CollaborationService = class {
3357
4240
  hasConversation(conversationId) {
3358
4241
  return this.#read().some((row) => row.kind === "conversation" && row.id === conversationId);
3359
4242
  }
3360
- roomSnapshot(kind, id, sessionId) {
4243
+ roomSnapshot(kind, id2, sessionId) {
3361
4244
  const actor = this.#actor(sessionId);
3362
- const row = this.#room(this.#read(), kind, id);
3363
- return { kind, id, workspaceId: this.#options.workspaceId, revision: row.revision, viewer: this.#viewer(actor), canWrite: actor.role !== "viewer", members: structuredClone(row.members), presence: this.#presenceFor(kind, id) };
4245
+ const row = this.#room(this.#read(), kind, id2);
4246
+ return { kind, id: id2, workspaceId: this.#options.workspaceId, revision: row.revision, viewer: this.#viewer(actor), canWrite: actor.role !== "viewer", members: structuredClone(row.members), presence: this.#presenceFor(kind, id2) };
3364
4247
  }
3365
4248
  snapshot(conversationId, sessionId) {
3366
4249
  const { kind: _kind, id: _id, ...snapshot } = this.roomSnapshot("conversation", conversationId, sessionId);
@@ -3374,19 +4257,19 @@ var CollaborationService = class {
3374
4257
  });
3375
4258
  return { workspaceId: this.#options.workspaceId, viewer: this.#viewer(actor), rooms };
3376
4259
  }
3377
- heartbeatRoom(kind, id, sessionId, input) {
4260
+ heartbeatRoom(kind, id2, sessionId, input) {
3378
4261
  const actor = this.#actor(sessionId);
3379
4262
  const parsed = external_exports.object({ typing: external_exports.boolean(), viewing: external_exports.boolean().default(true), clientId: external_exports.string().uuid().optional() }).strict().safeParse(input);
3380
4263
  if (!parsed.success) throw new CollaborationError("Invalid presence update.");
3381
- this.#presenceFor(kind, id);
4264
+ this.#presenceFor(kind, id2);
3382
4265
  let joined = false;
3383
4266
  this.#transaction((rows) => {
3384
- const row = this.#room(rows, kind, id);
4267
+ const row = this.#room(rows, kind, id2);
3385
4268
  const revision2 = row.revision;
3386
4269
  this.#join(row, actor);
3387
4270
  joined = row.revision !== revision2;
3388
4271
  });
3389
- const key = this.#roomKey(kind, id);
4272
+ const key = this.#roomKey(kind, id2);
3390
4273
  let entries = this.#presence.get(key);
3391
4274
  if (!entries) {
3392
4275
  entries = /* @__PURE__ */ new Map();
@@ -3398,7 +4281,7 @@ var CollaborationService = class {
3398
4281
  if (parsed.data.viewing) entries.set(entryKey, { ...this.#attribution(actor), sessionId, viewing: true, typing: parsed.data.typing, updatedAt: this.#now() });
3399
4282
  else entries.delete(entryKey);
3400
4283
  if (joined || previous?.typing !== (parsed.data.viewing ? parsed.data.typing : void 0) || previous?.displayName !== (parsed.data.viewing ? actor.displayName : void 0)) this.#notifyPresence();
3401
- return this.roomSnapshot(kind, id, sessionId);
4284
+ return this.roomSnapshot(kind, id2, sessionId);
3402
4285
  }
3403
4286
  heartbeat(conversationId, sessionId, input) {
3404
4287
  this.heartbeatRoom("conversation", conversationId, sessionId, input);
@@ -3457,29 +4340,133 @@ async function handleTeamRequest(req, url, auth, collaboration, gateway, readBod
3457
4340
  if (resource2) {
3458
4341
  if (method !== "POST") throw new TeamAuthError("Method not allowed.", 405);
3459
4342
  const kind = resource2[1];
3460
- const id2 = resource2[2];
3461
- if (kind === "conversation") gateway.get(id2);
3462
- else if (validateResource) await validateResource(kind, id2);
4343
+ const id3 = resource2[2];
4344
+ if (kind === "conversation") gateway.get(id3);
4345
+ else if (validateResource) await validateResource(kind, id3);
3463
4346
  else throw new TeamAuthError("Resource presence is not configured for this engine.", 404);
3464
- collaboration.registerRoom(kind, id2, session);
3465
- return { status: 200, data: collaboration.heartbeatRoom(kind, id2, session, await readBody()) };
4347
+ collaboration.registerRoom(kind, id3, session);
4348
+ return { status: 200, data: collaboration.heartbeatRoom(kind, id3, session, await readBody()) };
3466
4349
  }
3467
4350
  const match = /^conversations\/([A-Za-z0-9_.:-]+)(?:\/(presence))?$/.exec(path);
3468
4351
  if (!match) throw new TeamAuthError("Team resource not found.", 404);
3469
- const id = match[1];
3470
- gateway.get(id);
3471
- if (!collaboration.hasConversation(id)) collaboration.register(id, session);
3472
- if (!match[2] && method === "GET") return { status: 200, data: collaboration.snapshot(id, session) };
3473
- if (match[2] === "presence" && method === "POST") return { status: 200, data: collaboration.heartbeat(id, session, await readBody()) };
4352
+ const id2 = match[1];
4353
+ gateway.get(id2);
4354
+ if (!collaboration.hasConversation(id2)) collaboration.register(id2, session);
4355
+ if (!match[2] && method === "GET") return { status: 200, data: collaboration.snapshot(id2, session) };
4356
+ if (match[2] === "presence" && method === "POST") return { status: 200, data: collaboration.heartbeat(id2, session, await readBody()) };
3474
4357
  throw new TeamAuthError("Method not allowed.", 405);
3475
4358
  }
3476
- function assertTeamConsoleMutation(req, id, auth, collaboration, gateway) {
4359
+ function assertTeamConsoleMutation(req, id2, auth, collaboration, gateway) {
3477
4360
  if (!auth.enabled) return;
3478
- gateway.get(id);
4361
+ gateway.get(id2);
3479
4362
  const session = getSessionId(req);
3480
4363
  if (!session || !collaboration) throw new TeamAuthError("Sign in to this workspace.");
3481
- if (!collaboration.hasConversation(id)) collaboration.register(id, session);
3482
- collaboration.assertWriter(id, session);
4364
+ if (!collaboration.hasConversation(id2)) collaboration.register(id2, session);
4365
+ collaboration.assertWriter(id2, session);
4366
+ }
4367
+
4368
+ // packages/cli/src/web/browser-live-routes.ts
4369
+ var registry = { list: listBrowserViews, subscribe: subscribeBrowserView };
4370
+ var HEARTBEAT_MS = 15e3;
4371
+ var STREAM_LIFETIME_MS = 5 * 6e4;
4372
+ async function handleBrowserLiveRequest(req, res, url, ownerId, pageId, source = registry) {
4373
+ const reply = (status, body) => {
4374
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
4375
+ res.end(JSON.stringify(body));
4376
+ };
4377
+ if (url.search) {
4378
+ reply(400, { error: "Browser view endpoints do not accept query parameters." });
4379
+ return;
4380
+ }
4381
+ if (req.method !== "GET") {
4382
+ reply(405, { error: "Use GET for browser views." });
4383
+ return;
4384
+ }
4385
+ const views = source.list(ownerId);
4386
+ if (!pageId) {
4387
+ reply(200, { views });
4388
+ return;
4389
+ }
4390
+ const view = views.find((item) => item.pageId === pageId);
4391
+ if (!view) {
4392
+ reply(404, { error: "Browser view was closed or does not belong to this conversation." });
4393
+ return;
4394
+ }
4395
+ if (!view.liveAvailable) {
4396
+ reply(409, { error: "Live browser view is unavailable for this execution backend." });
4397
+ return;
4398
+ }
4399
+ res.writeHead(200, {
4400
+ "Content-Type": "text/event-stream",
4401
+ "Cache-Control": "no-cache, no-store, no-transform",
4402
+ "X-Accel-Buffering": "no",
4403
+ Connection: "keep-alive"
4404
+ });
4405
+ res.flushHeaders?.();
4406
+ let closed = false;
4407
+ let blocked = false;
4408
+ let unsubscribe;
4409
+ let heartbeat;
4410
+ let lifetime;
4411
+ const cleanup = () => {
4412
+ if (closed) return;
4413
+ closed = true;
4414
+ clearInterval(heartbeat);
4415
+ clearTimeout(lifetime);
4416
+ unsubscribe?.();
4417
+ unsubscribe = void 0;
4418
+ res.off("drain", drain);
4419
+ res.off("close", cleanup);
4420
+ res.off("error", cleanup);
4421
+ req.off("aborted", cleanup);
4422
+ };
4423
+ const drain = () => {
4424
+ blocked = false;
4425
+ };
4426
+ const write = (event, data) => {
4427
+ if (closed || blocked || res.destroyed || res.writableEnded) return;
4428
+ blocked = !res.write(`event: ${event}
4429
+ data: ${JSON.stringify(data)}
4430
+
4431
+ `);
4432
+ };
4433
+ const finish = (status) => {
4434
+ write("state", { status });
4435
+ cleanup();
4436
+ if (!res.destroyed && !res.writableEnded) res.end();
4437
+ };
4438
+ res.on("drain", drain);
4439
+ res.on("close", cleanup);
4440
+ res.on("error", cleanup);
4441
+ req.on("aborted", cleanup);
4442
+ write("state", { status: "connecting" });
4443
+ heartbeat = setInterval(() => {
4444
+ try {
4445
+ if (!source.list(ownerId).some((item) => item.pageId === pageId)) {
4446
+ finish("closed");
4447
+ return;
4448
+ }
4449
+ if (!closed && !blocked) blocked = !res.write(": heartbeat\n\n");
4450
+ } catch {
4451
+ finish("unavailable");
4452
+ }
4453
+ }, HEARTBEAT_MS);
4454
+ lifetime = setTimeout(() => finish("expired"), STREAM_LIFETIME_MS);
4455
+ heartbeat.unref?.();
4456
+ lifetime.unref?.();
4457
+ try {
4458
+ const stop = await source.subscribe(ownerId, pageId, (frame) => {
4459
+ if (closed || blocked || frame.mimeType !== "image/jpeg" || frame.data.length > 4 * 1024 * 1024) return;
4460
+ write("frame", frame);
4461
+ });
4462
+ if (closed) stop();
4463
+ else {
4464
+ unsubscribe = stop;
4465
+ write("state", { status: "live" });
4466
+ }
4467
+ } catch {
4468
+ finish("unavailable");
4469
+ }
3483
4470
  }
3484
4471
 
3485
4472
  // packages/cli/src/web/team-presence-stream.ts
@@ -3701,7 +4688,7 @@ function openBrowser(url) {
3701
4688
  execFile2(cmd, args, () => {
3702
4689
  });
3703
4690
  }
3704
- function json(res, status, body) {
4691
+ function json2(res, status, body) {
3705
4692
  res.writeHead(status, {
3706
4693
  "Content-Type": "application/json; charset=utf-8",
3707
4694
  "Cache-Control": "no-store"
@@ -4387,7 +5374,7 @@ function requireControlToken(req, res, controlToken, engineBearer) {
4387
5374
  const matches = (a, b) => typeof a === "string" && Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual3(Buffer.from(a), Buffer.from(b));
4388
5375
  const bearer = typeof req.headers.authorization === "string" && req.headers.authorization.startsWith("Bearer ") ? req.headers.authorization.slice(7) : void 0;
4389
5376
  if (!matches(provided, controlToken) && !matches(bearer, controlToken) && !(engineBearer && matches(bearer, engineBearer))) {
4390
- json(res, 403, { error: "Invalid or missing control token" });
5377
+ json2(res, 403, { error: "Invalid or missing control token" });
4391
5378
  return false;
4392
5379
  }
4393
5380
  return true;
@@ -4414,52 +5401,52 @@ async function handleWebConsoleApiRequest(req, res, requestUrl, gateway, operato
4414
5401
  const bodyId = input && typeof input === "object" && "sessionId" in input && typeof input.sessionId === "string" ? input.sessionId : void 0;
4415
5402
  const savedId = /^saved\/([^/]+)/.exec(path)?.[1];
4416
5403
  const savedLiveId = savedId ? gateway.list().find((item) => item.id === savedId || item.savedId === savedId)?.id : void 0;
4417
- const id = liveId ?? bodyId ?? savedLiveId;
4418
- if (id && !readResume) assertTeamConsoleMutation(req, id, team.auth, team.collaboration, gateway);
5404
+ const id2 = liveId ?? bodyId ?? savedLiveId;
5405
+ if (id2 && !readResume) assertTeamConsoleMutation(req, id2, team.auth, team.collaboration, gateway);
4419
5406
  }
4420
5407
  try {
4421
5408
  if (path === "execution" && method === "GET") {
4422
- json(res, 200, await workbenchStatus());
5409
+ json2(res, 200, await workbenchStatus());
4423
5410
  return true;
4424
5411
  }
4425
5412
  if (path === "github" && method === "GET") {
4426
- json(res, 200, { github: await github.status() });
5413
+ json2(res, 200, { github: await github.status() });
4427
5414
  return true;
4428
5415
  }
4429
5416
  if (path === "github/connect" && method === "POST") {
4430
- json(res, 200, { github: await github.connect(input) });
5417
+ json2(res, 200, { github: await github.connect(input) });
4431
5418
  return true;
4432
5419
  }
4433
5420
  if (path === "github/device-auth" && method === "POST") {
4434
- json(res, 202, { github: await github.start() });
5421
+ json2(res, 202, { github: await github.start() });
4435
5422
  return true;
4436
5423
  }
4437
5424
  if (path === "github/device-auth" && method === "DELETE") {
4438
- json(res, 200, { github: await github.cancel() });
5425
+ json2(res, 200, { github: await github.cancel() });
4439
5426
  return true;
4440
5427
  }
4441
5428
  const trigger = await triggers.handle(method, requestUrl, input);
4442
5429
  if (trigger) {
4443
- json(res, trigger.status, trigger.data);
5430
+ json2(res, trigger.status, trigger.data);
4444
5431
  return true;
4445
5432
  }
4446
5433
  const workflow = await workflows.handle(requestUrl.pathname, method, input, requestUrl.searchParams);
4447
5434
  if (workflow) {
4448
5435
  if (team?.collaboration && method !== "GET" && method !== "HEAD" && workflow.status >= 200 && workflow.status < 300) {
4449
5436
  const definition = workflow.data && typeof workflow.data === "object" && "definition" in workflow.data ? workflow.data.definition : void 0;
4450
- const id2 = definition && typeof definition === "object" && "id" in definition && typeof definition.id === "string" ? definition.id : /^\/api\/console\/workflow-definitions\/([^/]+)/.exec(requestUrl.pathname)?.[1];
4451
- if (id2) team.collaboration.notifyChanged("workflow", decodeURIComponent(id2));
5437
+ const id3 = definition && typeof definition === "object" && "id" in definition && typeof definition.id === "string" ? definition.id : /^\/api\/console\/workflow-definitions\/([^/]+)/.exec(requestUrl.pathname)?.[1];
5438
+ if (id3) team.collaboration.notifyChanged("workflow", decodeURIComponent(id3));
4452
5439
  }
4453
- json(res, workflow.status, workflow.data);
5440
+ json2(res, workflow.status, workflow.data);
4454
5441
  return true;
4455
5442
  }
4456
5443
  const service = await operator.handle(requestUrl.pathname, method, input, requestUrl.searchParams);
4457
5444
  if (service) {
4458
- json(res, service.status, service.data);
5445
+ json2(res, service.status, service.data);
4459
5446
  return true;
4460
5447
  }
4461
5448
  if (path === "search" && method === "GET") {
4462
- json(res, 200, await gateway.search({
5449
+ json2(res, 200, await gateway.search({
4463
5450
  q: requestUrl.searchParams.get("q") ?? "",
4464
5451
  ...requestUrl.searchParams.has("limit") ? { limit: Number(requestUrl.searchParams.get("limit")) } : {},
4465
5452
  ...requestUrl.searchParams.has("offset") ? { offset: Number(requestUrl.searchParams.get("offset")) } : {}
@@ -4467,84 +5454,99 @@ async function handleWebConsoleApiRequest(req, res, requestUrl, gateway, operato
4467
5454
  return true;
4468
5455
  }
4469
5456
  if (path === "sessions") {
4470
- if (method === "GET") json(res, 200, { sessions: gateway.list() });
5457
+ if (method === "GET") json2(res, 200, { sessions: gateway.list() });
4471
5458
  else if (method === "POST") {
4472
5459
  const session = gateway.create(input);
4473
5460
  if (team?.auth.enabled && team.collaboration) team.collaboration.register(session.id, getSessionId(req));
4474
- json(res, 201, { session });
4475
- } else json(res, 405, { error: "Method not allowed." });
5461
+ json2(res, 201, { session });
5462
+ } else json2(res, 405, { error: "Method not allowed." });
4476
5463
  return true;
4477
5464
  }
4478
5465
  const parts = path.split("/").map((part) => decodeURIComponent(part));
4479
5466
  if (parts.some((part) => !/^[A-Za-z0-9_.:-]+$/.test(part))) throw new ConsoleGatewayError("Invalid resource identifier.", 400);
4480
5467
  if (parts[0] === "saved") {
4481
- const id2 = parts[1];
4482
- if (!id2 && method === "GET") {
5468
+ const id3 = parts[1];
5469
+ if (!id3 && method === "GET") {
4483
5470
  const limit = requestUrl.searchParams.get("limit");
4484
- json(res, 200, { sessions: gateway.listSaved({
5471
+ json2(res, 200, { sessions: gateway.listSaved({
4485
5472
  ...requestUrl.searchParams.get("cwd") ? { cwd: requestUrl.searchParams.get("cwd") } : {},
4486
5473
  ...limit ? { limit: Number(limit) } : {}
4487
5474
  }) });
4488
- } else if (id2 && parts.length === 2 && method === "GET") json(res, 200, gateway.loadSaved(id2));
4489
- else if (id2 && parts.length === 2 && method === "DELETE") {
4490
- gateway.deleteSaved(id2);
4491
- json(res, 200, { ok: true });
4492
- } else if (id2 && parts[2] === "archive" && method === "POST") json(res, 200, { session: gateway.archiveSaved(id2, input) });
4493
- else if (id2 && parts[2] === "resume" && method === "POST") json(res, 201, { session: await gateway.resume(id2, input) });
4494
- else if (id2 && parts[2] === "export" && method === "GET") json(res, 200, gateway.exportSaved(id2));
4495
- else json(res, 405, { error: "Method not allowed." });
5475
+ } else if (id3 && parts.length === 2 && method === "GET") json2(res, 200, gateway.loadSaved(id3));
5476
+ else if (id3 && parts.length === 2 && method === "DELETE") {
5477
+ gateway.deleteSaved(id3);
5478
+ json2(res, 200, { ok: true });
5479
+ } else if (id3 && parts[2] === "archive" && method === "POST") json2(res, 200, { session: gateway.archiveSaved(id3, input) });
5480
+ else if (id3 && parts[2] === "resume" && method === "POST") json2(res, 201, { session: await gateway.resume(id3, input) });
5481
+ else if (id3 && parts[2] === "export" && method === "GET") json2(res, 200, gateway.exportSaved(id3));
5482
+ else json2(res, 405, { error: "Method not allowed." });
4496
5483
  return true;
4497
5484
  }
4498
5485
  if (parts[0] !== "sessions" || !parts[1]) return false;
4499
- const id = parts[1];
5486
+ const id2 = parts[1];
4500
5487
  const action = parts[2];
4501
- if (!action && method === "GET") json(res, 200, { snapshot: gateway.get(id) });
5488
+ if (action === "browser" && parts[3] === "views" && (parts.length === 4 || parts.length === 6 && parts[5] === "stream")) {
5489
+ gateway.get(id2);
5490
+ if (team?.auth.enabled) {
5491
+ const sessionId = getSessionId(req);
5492
+ if (!sessionId || !team.collaboration) throw new WebRequestError("Sign in to this workspace.", 403);
5493
+ if (!team.collaboration.hasConversation(id2)) team.collaboration.register(id2, sessionId);
5494
+ team.collaboration.snapshot(id2, sessionId);
5495
+ }
5496
+ await handleBrowserLiveRequest(req, res, requestUrl, id2, parts[4]);
5497
+ return true;
5498
+ }
5499
+ if (!action && method === "GET") json2(res, 200, { snapshot: gateway.get(id2) });
4502
5500
  else if (!action && method === "DELETE") {
4503
- workflows.cancelSession(id);
4504
- await gateway.close(id);
4505
- json(res, 200, { ok: true });
5501
+ workflows.cancelSession(id2);
5502
+ await gateway.close(id2);
5503
+ json2(res, 200, { ok: true });
4506
5504
  } else if (action === "events" && method === "GET") {
4507
- json(res, 200, gateway.eventsAfter(id, consoleEventsAfter(requestUrl.searchParams.get("after"))));
5505
+ json2(res, 200, gateway.eventsAfter(id2, consoleEventsAfter(requestUrl.searchParams.get("after"))));
4508
5506
  } else if (action === "messages" && method === "POST") {
4509
5507
  const message2 = author && input && typeof input === "object" && !("workerId" in input) && (!("mode" in input) || input.mode === "send") ? { ...input, mode: "queue" } : input;
4510
- json(res, 202, { session: await gateway.send(id, message2, author) });
4511
- } else if (action === "cancel" && method === "POST") json(res, 200, { session: await gateway.cancel(id) });
5508
+ const { queuedMessageId, ...session } = await gateway.send(id2, message2, author);
5509
+ json2(res, 202, { session, ...queuedMessageId ? { queuedMessageId } : {} });
5510
+ } else if (action === "cancel" && method === "POST") json2(res, 200, { session: await gateway.cancel(id2) });
4512
5511
  else if (action === "connection" && parts[3] === "reload" && parts.length === 4 && method === "POST") {
4513
5512
  external_exports.object({}).strict().parse(input ?? {});
4514
- json(res, 200, { session: await gateway.reloadConnection(id) });
4515
- } else if (action === "configuration" && method === "PATCH") json(res, 200, { session: await gateway.configure(id, input) });
4516
- else if (action === "clear" && method === "POST") json(res, 200, { session: await gateway.clear(id) });
4517
- else if (action === "continue" && method === "POST") json(res, 202, { session: await gateway.continue(id, input, author) });
4518
- else if (action === "harness" && method === "POST") json(res, 200, await gateway.harness(id, input));
4519
- else if (action === "save" && method === "POST") json(res, 200, { session: gateway.save(id) });
5513
+ json2(res, 200, { session: await gateway.reloadConnection(id2) });
5514
+ } else if (action === "configuration" && method === "PATCH") json2(res, 200, { session: await gateway.configure(id2, input) });
5515
+ else if (action === "clear" && method === "POST") json2(res, 200, { session: await gateway.clear(id2) });
5516
+ else if (action === "continue" && method === "POST") json2(res, 202, { session: await gateway.continue(id2, input, author) });
5517
+ else if (action === "harness" && method === "POST") json2(res, 200, await gateway.harness(id2, input));
5518
+ else if (action === "save" && method === "POST") json2(res, 200, { session: gateway.save(id2) });
4520
5519
  else if (action === "archive" && method === "POST") {
4521
- workflows.cancelSession(id);
4522
- json(res, 200, { session: await gateway.archive(id) });
5520
+ workflows.cancelSession(id2);
5521
+ json2(res, 200, { session: await gateway.archive(id2) });
4523
5522
  } else if (action === "delete" && method === "POST") {
4524
- workflows.cancelSession(id);
4525
- json(res, 200, await gateway.delete(id));
4526
- } else if (action === "export" && method === "GET") json(res, 200, gateway.export(id));
4527
- else if (action === "queue" && method === "DELETE") json(res, 200, { session: gateway.removeQueued(id, parts[3]) });
4528
- else if (action === "decisions" && parts[3] && method === "POST") json(res, 200, { session: gateway.resolveDecision(id, parts[3], input) });
5523
+ workflows.cancelSession(id2);
5524
+ json2(res, 200, await gateway.delete(id2));
5525
+ } else if (action === "export" && method === "GET") json2(res, 200, gateway.export(id2));
5526
+ else if (action === "queue" && parts[3] && parts[4] === "send" && parts.length === 5 && method === "POST") {
5527
+ external_exports.object({}).strict().parse(input ?? {});
5528
+ json2(res, 202, { session: gateway.sendQueuedNow(id2, parts[3]) });
5529
+ } else if (action === "queue" && method === "DELETE") json2(res, 200, { session: gateway.removeQueued(id2, parts[3]) });
5530
+ else if (action === "decisions" && parts[3] && method === "POST") json2(res, 200, { session: gateway.resolveDecision(id2, parts[3], input) });
4529
5531
  else if (action === "workers") {
4530
5532
  const workerId = parts[3];
4531
- if (!workerId && method === "GET") json(res, 200, { workers: gateway.workers(id) });
4532
- else if (workerId === "stop" && method === "POST") json(res, 200, { session: await gateway.drainWorkers(id) });
4533
- else if (workerId && parts[4] === "export" && method === "GET") json(res, 200, gateway.exportWorker(id, workerId));
4534
- else if (workerId && parts[4] === "stop" && method === "POST") json(res, 200, { worker: await gateway.stopWorker(id, workerId) });
5533
+ if (!workerId && method === "GET") json2(res, 200, { workers: gateway.workers(id2) });
5534
+ else if (workerId === "stop" && method === "POST") json2(res, 200, { session: await gateway.drainWorkers(id2) });
5535
+ else if (workerId && parts[4] === "export" && method === "GET") json2(res, 200, gateway.exportWorker(id2, workerId));
5536
+ else if (workerId && parts[4] === "stop" && method === "POST") json2(res, 200, { worker: await gateway.stopWorker(id2, workerId) });
4535
5537
  else if (workerId && parts[4] === "messages" && method === "POST") {
4536
5538
  const body = external_exports.object({ text: external_exports.unknown() }).strict().parse(input);
4537
- json(res, 202, { session: gateway.sendWorker(id, workerId, body.text, author) });
4538
- } else json(res, 405, { error: "Method not allowed." });
4539
- } else json(res, 405, { error: "Method not allowed." });
5539
+ json2(res, 202, { session: gateway.sendWorker(id2, workerId, body.text, author) });
5540
+ } else json2(res, 405, { error: "Method not allowed." });
5541
+ } else json2(res, 405, { error: "Method not allowed." });
4540
5542
  return true;
4541
5543
  } catch (error2) {
4542
5544
  if (error2 instanceof external_exports.ZodError) {
4543
- json(res, 400, { error: "Invalid console request payload." });
5545
+ json2(res, 400, { error: "Invalid console request payload." });
4544
5546
  return true;
4545
5547
  }
4546
5548
  if (error2 instanceof ConsoleGatewayError) {
4547
- json(res, error2.statusCode, { error: error2.message });
5549
+ json2(res, error2.statusCode, { error: error2.message });
4548
5550
  return true;
4549
5551
  }
4550
5552
  throw error2;
@@ -4572,10 +5574,10 @@ data: ${JSON.stringify(event)}
4572
5574
  `);
4573
5575
  }
4574
5576
  async function handleApiRequest(req, res, pathname, dbPath, controlToken, engineBearer) {
4575
- const { osecDB: osecDB2 } = await import("./dist-RKLQIXDT.js");
5577
+ const { osecDB: osecDB2 } = await import("./dist-OLGNKDB2.js");
4576
5578
  if (isPresentationEventsStreamPath(pathname)) {
4577
5579
  if (req.method !== "GET") {
4578
- json(res, 405, { error: "Method not allowed" });
5580
+ json2(res, 405, { error: "Method not allowed" });
4579
5581
  return true;
4580
5582
  }
4581
5583
  const db = new osecDB2(dbPath);
@@ -4619,27 +5621,27 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4619
5621
  const controlPath = parseControlPath(pathname);
4620
5622
  if (controlPath) {
4621
5623
  if (req.method !== "POST") {
4622
- json(res, 405, { error: "Method not allowed" });
5624
+ json2(res, 405, { error: "Method not allowed" });
4623
5625
  return true;
4624
5626
  }
4625
5627
  if (!requireControlToken(req, res, controlToken, engineBearer)) {
4626
5628
  return true;
4627
5629
  }
4628
5630
  if (controlPath.action === "recover-stale-workers") {
4629
- const { recoverStaleWorkers } = await import("./orchestrate-OBS37FWQ.js");
5631
+ const { recoverStaleWorkers } = await import("./orchestrate-Z3DW265M.js");
4630
5632
  const body = await readJson(req);
4631
5633
  const recovered = recoverStaleWorkers(
4632
5634
  dbPath,
4633
5635
  typeof body.staleAfterMs === "number" ? body.staleAfterMs : 3e4
4634
5636
  );
4635
- json(res, 200, { ok: true, recovered });
5637
+ json2(res, 200, { ok: true, recovered });
4636
5638
  return true;
4637
5639
  }
4638
5640
  if (controlPath.action === "prune-stopped-workers") {
4639
5641
  const db = new osecDB2(dbPath);
4640
5642
  try {
4641
5643
  const deleted = db.deleteWorkersByStatus("stopped");
4642
- json(res, 200, { ok: true, deleted });
5644
+ json2(res, 200, { ok: true, deleted });
4643
5645
  } finally {
4644
5646
  db.close();
4645
5647
  }
@@ -4653,7 +5655,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4653
5655
  (worker) => worker.status !== "stopped" && Date.now() - Date.parse(worker.heartbeatAt) < 2e4
4654
5656
  );
4655
5657
  if (activeWorkers.length > 0) {
4656
- json(res, 409, { error: "An active daemon is already heartbeating. Stop it before starting another local daemon." });
5658
+ json2(res, 409, { error: "An active daemon is already heartbeating. Stop it before starting another local daemon." });
4657
5659
  return true;
4658
5660
  }
4659
5661
  } finally {
@@ -4664,21 +5666,21 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4664
5666
  label: typeof body.label === "string" && body.label.trim() ? body.label.trim() : "control-plane-1",
4665
5667
  pollIntervalMs: typeof body.pollIntervalMs === "number" ? body.pollIntervalMs : 2e3
4666
5668
  });
4667
- json(res, 200, { ok: true, ...started });
5669
+ json2(res, 200, { ok: true, ...started });
4668
5670
  return true;
4669
5671
  }
4670
5672
  if (controlPath.action === "stop-daemon") {
4671
5673
  const stopped = stopDaemonWorkers(osecDB2, dbPath);
4672
- json(res, 200, { ok: true, stopped });
5674
+ json2(res, 200, { ok: true, stopped });
4673
5675
  return true;
4674
5676
  }
4675
5677
  if (controlPath.action === "reset-database") {
4676
- const { resetOsecDatabase } = await import("./dist-RKLQIXDT.js");
4677
- const { seedVerificationWorkbench } = await import("./db-BXJNWUYJ.js");
5678
+ const { resetOsecDatabase } = await import("./dist-OLGNKDB2.js");
5679
+ const { seedVerificationWorkbench } = await import("./db-R2AQSESC.js");
4678
5680
  const body = await readJson(req);
4679
5681
  const seed = typeof body.seed === "string" ? body.seed.trim().toLowerCase() : "verification";
4680
5682
  if (!["verification", "empty"].includes(seed)) {
4681
- json(res, 400, { error: `Unsupported seed preset: ${seed}` });
5683
+ json2(res, 400, { error: `Unsupported seed preset: ${seed}` });
4682
5684
  return true;
4683
5685
  }
4684
5686
  const activeWorkerDb = new osecDB2(dbPath);
@@ -4687,7 +5689,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4687
5689
  (worker) => ["idle", "claiming", "running", "sleeping"].includes(worker.status) && Date.now() - Date.parse(worker.heartbeatAt) < 2e4 && (worker.pid ? isLiveLocalPid(worker.pid) : true)
4688
5690
  );
4689
5691
  if (hasActiveWorker) {
4690
- json(res, 409, { error: "Stop active orchestration daemons before resetting the local database." });
5692
+ json2(res, 409, { error: "Stop active orchestration daemons before resetting the local database." });
4691
5693
  return true;
4692
5694
  }
4693
5695
  } finally {
@@ -4697,7 +5699,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4697
5699
  const resetDb = new osecDB2(dbPath);
4698
5700
  try {
4699
5701
  const seeded = seed === "verification" ? seedVerificationWorkbench(resetDb) : { scans: 0, families: 0, workers: 0 };
4700
- json(res, 200, { ok: true, path, seed, ...seeded });
5702
+ json2(res, 200, { ok: true, path, seed, ...seeded });
4701
5703
  } finally {
4702
5704
  resetDb.close();
4703
5705
  }
@@ -4733,7 +5735,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4733
5735
  });
4734
5736
  const casesById = new Map(cases.map((item) => [item.id, item]));
4735
5737
  const workItemsById = new Map(workItems.map((item) => [item.id, item]));
4736
- json(res, 200, {
5738
+ json2(res, 200, {
4737
5739
  scans: scans.map(summarizeScan),
4738
5740
  cases,
4739
5741
  groups,
@@ -4749,7 +5751,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4749
5751
  const db = new osecDB2(dbPath);
4750
5752
  try {
4751
5753
  const scans = db.listScans(100);
4752
- json(res, 200, { scans: scans.map(summarizeScan) });
5754
+ json2(res, 200, { scans: scans.map(summarizeScan) });
4753
5755
  } finally {
4754
5756
  db.close();
4755
5757
  }
@@ -4783,7 +5785,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4783
5785
  presentation
4784
5786
  };
4785
5787
  });
4786
- json(res, 200, { events });
5788
+ json2(res, 200, { events });
4787
5789
  } finally {
4788
5790
  db.close();
4789
5791
  }
@@ -4795,7 +5797,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4795
5797
  try {
4796
5798
  const scan = db.getScan(scanPath.scanId);
4797
5799
  if (!scan) {
4798
- json(res, 404, { error: "Scan not found" });
5800
+ json2(res, 404, { error: "Scan not found" });
4799
5801
  return true;
4800
5802
  }
4801
5803
  if (scanPath.suffix === "events") {
@@ -4803,7 +5805,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4803
5805
  ...event,
4804
5806
  payload: parsePayload(event.payload)
4805
5807
  }));
4806
- json(res, 200, { scan: summarizeScan(scan), events });
5808
+ json2(res, 200, { scan: summarizeScan(scan), events });
4807
5809
  return true;
4808
5810
  }
4809
5811
  if (scanPath.suffix === "findings") {
@@ -4813,7 +5815,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4813
5815
  scanIds: [scanPath.scanId],
4814
5816
  status: "running"
4815
5817
  });
4816
- json(res, 200, {
5818
+ json2(res, 200, {
4817
5819
  scan: summarizeScan(scan),
4818
5820
  findings,
4819
5821
  groups: groupFindings(
@@ -4824,7 +5826,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4824
5826
  });
4825
5827
  return true;
4826
5828
  }
4827
- json(res, 200, { scan: summarizeScan(scan) });
5829
+ json2(res, 200, { scan: summarizeScan(scan) });
4828
5830
  } finally {
4829
5831
  db.close();
4830
5832
  }
@@ -4844,7 +5846,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4844
5846
  normalizeTriageStatus(body.triageStatus),
4845
5847
  typeof body.triageNote === "string" ? body.triageNote : void 0
4846
5848
  );
4847
- json(res, 200, { ok: true });
5849
+ json2(res, 200, { ok: true });
4848
5850
  return true;
4849
5851
  }
4850
5852
  if (req.method === "POST" && familyPath.action === "workflow") {
@@ -4857,12 +5859,12 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4857
5859
  normalizeWorkflowStatus(body.workflowStatus),
4858
5860
  typeof body.workflowAssignee === "string" ? body.workflowAssignee.trim() : null
4859
5861
  );
4860
- json(res, 200, { ok: true });
5862
+ json2(res, 200, { ok: true });
4861
5863
  return true;
4862
5864
  }
4863
5865
  const rows = db.getRelatedFindings(familyPath.fingerprint).map(normalizeFinding);
4864
5866
  if (rows.length === 0) {
4865
- json(res, 404, { error: "Not found" });
5867
+ json2(res, 404, { error: "Not found" });
4866
5868
  return true;
4867
5869
  }
4868
5870
  const verdicts = db.listVerdicts(rows.map((row) => row.id));
@@ -4904,7 +5906,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4904
5906
  sessions,
4905
5907
  events: allEvents
4906
5908
  });
4907
- json(res, 200, {
5909
+ json2(res, 200, {
4908
5910
  consoleCommand: buildFindingConsoleCommand(rows[0], dbPath),
4909
5911
  fingerprint: familyPath.fingerprint,
4910
5912
  case: familyCase,
@@ -4972,7 +5974,7 @@ function registerDashboardCommand(program) {
4972
5974
  const timeCapMs = opts.engineTimeCap === void 0 ? 6e5 : Number(opts.engineTimeCap);
4973
5975
  const costCapUsd = opts.engineCostCap === void 0 ? 5 : Number(opts.engineCostCap);
4974
5976
  if (!Number.isInteger(timeCapMs) || timeCapMs < 1 || timeCapMs > 864e5 || !Number.isFinite(costCapUsd) || costCapUsd <= 0 || costCapUsd > 1e3) throw new Error("Engine limits must be positive, with time at most 86400000 ms and cost at most $1000.");
4975
- const controlToken = randomUUID7();
5977
+ const controlToken = randomUUID9();
4976
5978
  let teamAuth = new TeamAuth({ configPath: teamConfigPath, origin });
4977
5979
  let teamDirectory = teamAuth.enabled ? join7(homeStateDir(), "teams", createHash8("sha256").update(teamAuth.workspaceId).digest("hex")) : void 0;
4978
5980
  if (teamDirectory) {
@@ -4982,9 +5984,51 @@ function registerDashboardCommand(program) {
4982
5984
  }
4983
5985
  let collaboration = teamAuth.enabled ? new CollaborationService({ workspaceId: teamAuth.workspaceId, stateDir: join7(teamDirectory, "collaboration"), resolveSession: (sessionId) => teamAuth.resolveSessionId(sessionId) }) : void 0;
4984
5986
  assertLocalEngineAvailable(opts.dbPath);
4985
- const capabilities = ["sessions", "workflows", "schedules", "approvals", "events", "workspaces", "artifacts", "model-connections", "operator-services", "process-controls", "learning", "workflow-engine"];
5987
+ const capabilities = ["sessions", "workflows", "schedules", "approvals", "events", "workspaces", "artifacts", "model-connections", "provider-relay", "operator-services", "process-controls", "learning", "workflow-engine"];
4986
5988
  let localHandshake = createBackendHandshake(opts.dbPath, capabilities);
4987
5989
  let backends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake });
5990
+ let modelBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake });
5991
+ const modelRegistry = {
5992
+ handshake: (id2) => {
5993
+ modelBackends.reload();
5994
+ return modelBackends.handshake(id2);
5995
+ },
5996
+ request: (...args) => {
5997
+ modelBackends.reload();
5998
+ return modelBackends.request(...args);
5999
+ }
6000
+ };
6001
+ let localProviderSessions = new LocalProviderSessions({ registry: modelRegistry });
6002
+ const hostRepairs = /* @__PURE__ */ new Map();
6003
+ const hostRepairAbort = new AbortController();
6004
+ const repairManagedHost = (backendId, signal) => {
6005
+ const pending = hostRepairs.get(backendId);
6006
+ if (pending) return pending;
6007
+ const compiled = import.meta.url.startsWith("file:///$bunfs/") || import.meta.url.startsWith("/$bunfs/");
6008
+ const args = [...compiled ? [] : [resolveCliEntrypoint()], "worker", "repair", backendId];
6009
+ const task = new Promise((resolveRepair, reject) => {
6010
+ execFile2(process.execPath, args, { timeout: 75e3, maxBuffer: 65536, signal: signal ? AbortSignal.any([signal, hostRepairAbort.signal]) : hostRepairAbort.signal }, (error2, stdout) => {
6011
+ if (error2) {
6012
+ reject(new WebRequestError("This host could not reconnect. Check its SSH connection and managed engine installation.", 409));
6013
+ return;
6014
+ }
6015
+ try {
6016
+ resolveRepair(JSON.parse(stdout));
6017
+ } catch {
6018
+ reject(new WebRequestError("Host reconnect returned an invalid result.", 502));
6019
+ }
6020
+ });
6021
+ }).finally(() => {
6022
+ hostRepairs.delete(backendId);
6023
+ });
6024
+ hostRepairs.set(backendId, task);
6025
+ return task;
6026
+ };
6027
+ let stopHostReconnect;
6028
+ const watchManagedHosts = () => startManagedHostReconnect({ getRegistry: () => backends, repair: repairManagedHost, onRecovered: async (id2) => {
6029
+ backends.reload();
6030
+ await backends.handshake(id2);
6031
+ } });
4988
6032
  let engine;
4989
6033
  let startupGateway;
4990
6034
  let startupOperator;
@@ -4995,8 +6039,9 @@ function registerDashboardCommand(program) {
4995
6039
  try {
4996
6040
  const { path: assetDir, cleanup: cleanupAssetDir } = resolveDashboardAssetDir(opts.assetDir);
4997
6041
  startupAssetCleanup = cleanupAssetDir;
4998
- const buildServices = (auth, dbPath = opts.dbPath, stateDir = teamDirectory) => createWorkspaceServices({ auth, dbPath, stateDir, workspace: opts.engineWorkspace, scopePath: opts.engineScope, target: opts.engineTarget, allowApply: opts.engineAllowApply, controlToken, engineBearer, timeCapMs, costCapUsd });
6042
+ const buildServices = (auth, dbPath = opts.dbPath, stateDir = teamDirectory, relayEpoch = localHandshake.serverInstanceId) => createWorkspaceServices({ relayEpoch, auth, dbPath, stateDir, workspace: opts.engineWorkspace, scopePath: opts.engineScope, target: opts.engineTarget, allowApply: opts.engineAllowApply, controlToken, engineBearer, timeCapMs, costCapUsd });
4999
6043
  let services = await buildServices(teamAuth);
6044
+ stopHostReconnect = watchManagedHosts();
5000
6045
  let { skills, gateway: consoleGateway, engagements, operator, workflows, triggers, github } = services;
5001
6046
  engine = services.engine;
5002
6047
  startupGateway = consoleGateway;
@@ -5015,11 +6060,11 @@ function registerDashboardCommand(program) {
5015
6060
  currentAuth: () => teamAuth,
5016
6061
  activate: async (prepared) => {
5017
6062
  if (consoleGateway.hasActiveTurns() || workflows.hasActiveWork()) throw new TeamAuthError("Finish active work before creating a team workspace.", 409);
5018
- const next = await buildServices(prepared.auth, prepared.dbPath, prepared.stateDir);
6063
+ const nextHandshake = createBackendHandshake(prepared.dbPath, capabilities);
6064
+ const next = await buildServices(prepared.auth, prepared.dbPath, prepared.stateDir, nextHandshake.serverInstanceId);
5019
6065
  let nextBackends;
5020
6066
  let nextUnregister;
5021
6067
  try {
5022
- const nextHandshake = createBackendHandshake(prepared.dbPath, capabilities);
5023
6068
  nextBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake: nextHandshake });
5024
6069
  const nextAccess = new FindingsAccessTokenStore({ workspaceId: prepared.workspaceId, stateDir: join7(prepared.stateDir, "findings-api-access") });
5025
6070
  const nextRead = new FindingsReadApi({ workspaceId: prepared.workspaceId, dbPath: prepared.dbPath });
@@ -5028,6 +6073,9 @@ function registerDashboardCommand(program) {
5028
6073
  if (consoleGateway.hasActiveTurns() || workflows.hasActiveWork()) throw new TeamAuthError("Finish active work before creating a team workspace.", 409);
5029
6074
  await services.dispose();
5030
6075
  for (const response of activeResponses) if (response !== setupResponse) response.end();
6076
+ stopHostReconnect?.();
6077
+ await localProviderSessions.dispose();
6078
+ modelBackends.dispose();
5031
6079
  backends.dispose();
5032
6080
  unregisterLocalEngine?.();
5033
6081
  services = next;
@@ -5037,6 +6085,8 @@ function registerDashboardCommand(program) {
5037
6085
  collaboration = nextCollaboration;
5038
6086
  localHandshake = nextHandshake;
5039
6087
  backends = nextBackends;
6088
+ modelBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake: nextHandshake });
6089
+ localProviderSessions = new LocalProviderSessions({ registry: modelRegistry });
5040
6090
  unregisterLocalEngine = nextUnregister;
5041
6091
  ({ skills, gateway: consoleGateway, engagements, operator, workflows, triggers, github } = next);
5042
6092
  engine = next.engine;
@@ -5047,6 +6097,7 @@ function registerDashboardCommand(program) {
5047
6097
  startupWorkflows = workflows;
5048
6098
  startupTriggers = triggers;
5049
6099
  startupGitHub = github;
6100
+ stopHostReconnect = watchManagedHosts();
5050
6101
  } catch (cause) {
5051
6102
  nextBackends?.dispose();
5052
6103
  nextUnregister?.();
@@ -5064,12 +6115,12 @@ function registerDashboardCommand(program) {
5064
6115
  try {
5065
6116
  authorizeWebRequest(req, origin);
5066
6117
  } catch (error2) {
5067
- json(res, error2 instanceof WebRequestError ? error2.statusCode : 403, { error: error2 instanceof Error ? error2.message : "Invalid request origin." });
6118
+ json2(res, error2 instanceof WebRequestError ? error2.statusCode : 403, { error: error2 instanceof Error ? error2.message : "Invalid request origin." });
5068
6119
  return;
5069
6120
  }
5070
6121
  let requestUrl = new URL2(req.url ?? "/", origin);
5071
6122
  const requestId = BackendRequestIdSchema.safeParse(req.headers["x-0-request-id"]);
5072
- res.setHeader("X-0-Request-ID", requestId.success ? requestId.data : randomUUID7());
6123
+ res.setHeader("X-0-Request-ID", requestId.success ? requestId.data : randomUUID9());
5073
6124
  try {
5074
6125
  if (requestUrl.pathname.startsWith("/api/")) {
5075
6126
  const findingsCredential = findingsAccess.authenticate(req, requestUrl);
@@ -5078,7 +6129,7 @@ function registerDashboardCommand(program) {
5078
6129
  if (!findingsCredential) throw new FindingsAccessError("A findings API key is required in the Authorization header.", 401);
5079
6130
  const reply = handleFindingsReadRequest(req, requestUrl, findingsRead);
5080
6131
  if (!reply) throw new FindingsAccessError("Findings API route was not found.", 404);
5081
- json(res, reply.status, reply.data);
6132
+ json2(res, reply.status, reply.data);
5082
6133
  return;
5083
6134
  }
5084
6135
  const ssoNavigation = requestUrl.pathname === "/api/team/auth/sso" || requestUrl.pathname === "/api/team/auth/callback";
@@ -5089,7 +6140,7 @@ function registerDashboardCommand(program) {
5089
6140
  const reply = await handleTeamSetupRequest(req, requestUrl, setup, () => readJson(req));
5090
6141
  if (reply) {
5091
6142
  for (const [name, value] of Object.entries(reply.headers ?? {})) res.setHeader(name, value);
5092
- json(res, reply.status, reply.data);
6143
+ json2(res, reply.status, reply.data);
5093
6144
  return;
5094
6145
  }
5095
6146
  } finally {
@@ -5098,12 +6149,12 @@ function registerDashboardCommand(program) {
5098
6149
  }
5099
6150
  if (setup.status().pending && requestUrl.pathname !== "/api/team/session") throw new WebRequestError("Workspace setup is in progress. Try again shortly.", 503);
5100
6151
  if (handleTeamPresenceStream(req, res, requestUrl, teamAuth, collaboration)) return;
5101
- const teamReply = await handleTeamRequest(req, requestUrl, teamAuth, collaboration, consoleGateway, () => readJson(req), async (kind, id) => {
6152
+ const teamReply = await handleTeamRequest(req, requestUrl, teamAuth, collaboration, consoleGateway, () => readJson(req), async (kind, id2) => {
5102
6153
  if (kind === "report") {
5103
- engagements.get(id);
6154
+ engagements.get(id2);
5104
6155
  return;
5105
6156
  }
5106
- const definition = await workflows.handle(`/api/console/workflow-definitions/${encodeURIComponent(id)}`, "GET", void 0, new URLSearchParams());
6157
+ const definition = await workflows.handle(`/api/console/workflow-definitions/${encodeURIComponent(id2)}`, "GET", void 0, new URLSearchParams());
5107
6158
  if (!definition || definition.status !== 200) throw new WebRequestError("Workflow was not found.", 404);
5108
6159
  });
5109
6160
  if (teamReply) {
@@ -5111,7 +6162,7 @@ function registerDashboardCommand(program) {
5111
6162
  if (teamReply.redirect) {
5112
6163
  res.writeHead(teamReply.status, { Location: teamReply.redirect, "Cache-Control": "no-store" });
5113
6164
  res.end();
5114
- } else json(res, teamReply.status, teamReply.data);
6165
+ } else json2(res, teamReply.status, teamReply.data);
5115
6166
  return;
5116
6167
  }
5117
6168
  const bearer = req.headers.authorization;
@@ -5122,11 +6173,41 @@ function registerDashboardCommand(program) {
5122
6173
  if (expectedEngineId !== void 0 && expectedEngineId !== localHandshake.engineId) throw new WebRequestError("Request belongs to a different engine identity.", 409);
5123
6174
  const findingsAccessReply = await handleFindingsAccessTokenRequest(req, requestUrl, teamAuth, findingsAccess, () => readJson(req));
5124
6175
  if (findingsAccessReply) {
5125
- json(res, findingsAccessReply.status, findingsAccessReply.data);
6176
+ json2(res, findingsAccessReply.status, findingsAccessReply.data);
6177
+ return;
6178
+ }
6179
+ const remoteSessionRoute = /^\/api\/console\/remote-sessions(?:\/([^/]+)\/(configuration|status))?$/.exec(requestUrl.pathname);
6180
+ if (remoteSessionRoute) {
6181
+ const ownerId = engineRequest ? "engine" : teamAuth.resolveSession(req)?.userId ?? "local";
6182
+ try {
6183
+ if (remoteSessionRoute[1] && remoteSessionRoute[2] === "status" && req.method === "GET") {
6184
+ const backendId = BackendDescriptorSchema.shape.id.parse(requestUrl.searchParams.get("backendId"));
6185
+ json2(res, 200, await localProviderSessions.status(backendId, decodeURIComponent(remoteSessionRoute[1]), ownerId));
6186
+ } else if (!remoteSessionRoute[1] && req.method === "POST") {
6187
+ const body = external_exports.object({ backendId: external_exports.string().min(1).max(128), configuration: external_exports.unknown() }).strict().parse(await readJson(req));
6188
+ json2(res, 201, await localProviderSessions.create(body.backendId, body.configuration, ownerId));
6189
+ } else if (remoteSessionRoute[1] && remoteSessionRoute[2] === "configuration" && req.method === "PATCH") {
6190
+ const body = external_exports.object({ backendId: external_exports.string().min(1).max(128), runtime: external_exports.unknown() }).strict().parse(await readJson(req));
6191
+ json2(res, 200, await localProviderSessions.configure(body.backendId, decodeURIComponent(remoteSessionRoute[1]), body.runtime, ownerId));
6192
+ } else throw new WebRequestError("Method not allowed.", 405);
6193
+ } catch (error2) {
6194
+ if (error2 instanceof LocalProviderSessionError) {
6195
+ json2(res, error2.statusCode, { error: error2.message });
6196
+ return;
6197
+ }
6198
+ throw error2;
6199
+ }
6200
+ return;
6201
+ }
6202
+ if (requestUrl.pathname === "/api/console/provider-relays" || requestUrl.pathname.startsWith("/api/console/provider-relays/")) {
6203
+ if (!engineRequest) throw new WebRequestError("Provider relay transport requires an authenticated engine connection.", 403);
6204
+ if (await handleRemoteProviderRelayRequest(req, res, requestUrl, services.remoteProviderRelays, () => readJson(req))) return;
6205
+ }
6206
+ const backendRoute = await handleBackendConnectionRequest(req, res, requestUrl, backends, (backendId, sessionId) => localProviderSessions.release(backendId, sessionId, engineRequest ? "engine" : teamAuth.resolveSession(req)?.userId ?? "local"));
6207
+ if (backendRoute.handled) {
6208
+ modelBackends.reload();
5126
6209
  return;
5127
6210
  }
5128
- const backendRoute = await handleBackendConnectionRequest(req, res, requestUrl, backends);
5129
- if (backendRoute.handled) return;
5130
6211
  if (backendRoute.localPath) requestUrl = new URL2(backendRoute.localPath, origin);
5131
6212
  const skillsRoute = requestUrl.pathname.match(/^\/api\/skills(?:\/(.*))?$/);
5132
6213
  if (skillsRoute) {
@@ -5140,21 +6221,21 @@ function registerDashboardCommand(program) {
5140
6221
  throw new WebRequestError("Invalid skill identifier.", 400);
5141
6222
  }
5142
6223
  const method = req.method ?? "GET";
5143
- if (!path && method === "GET") json(res, 200, skills.list(access));
5144
- else if (!path && method === "POST") json(res, 201, { skill: skills.create(await readJson(req)) });
5145
- else if (path === "import" && method === "POST") json(res, 201, { skill: skills.import(await readJson(req, 12 * 1024 * 1024)) });
5146
- else if (path === "mounts" && method === "POST") json(res, 201, { mount: skills.mount(await readJson(req)) });
6224
+ if (!path && method === "GET") json2(res, 200, skills.list(access));
6225
+ else if (!path && method === "POST") json2(res, 201, { skill: skills.create(await readJson(req)) });
6226
+ else if (path === "import" && method === "POST") json2(res, 201, { skill: skills.import(await readJson(req, 12 * 1024 * 1024)) });
6227
+ else if (path === "mounts" && method === "POST") json2(res, 201, { mount: skills.mount(await readJson(req)) });
5147
6228
  else if (path.startsWith("mounts/") && method === "DELETE") {
5148
6229
  skills.unmount(decodedPath.slice(7));
5149
- json(res, 200, { ok: true });
5150
- } else if (path.endsWith("/copy") && method === "POST") json(res, 201, { skill: skills.copy(decodedPath.slice(0, -5), await readJson(req), access) });
6230
+ json2(res, 200, { ok: true });
6231
+ } else if (path.endsWith("/copy") && method === "POST") json2(res, 201, { skill: skills.copy(decodedPath.slice(0, -5), await readJson(req), access) });
5151
6232
  else if (path.endsWith("/export") && method === "GET") {
5152
- const id = decodedPath.slice(0, -7);
5153
- const bundle = skills.export(id);
6233
+ const id2 = decodedPath.slice(0, -7);
6234
+ const bundle = skills.export(id2);
5154
6235
  res.setHeader("Content-Disposition", `attachment; filename="${bundle.name}.skill.json"`);
5155
- json(res, 200, bundle);
5156
- } else if (path && method === "GET") json(res, 200, { skill: skills.get(decodedPath, access) });
5157
- else if (path && method === "PUT") json(res, 200, { skill: skills.update(decodedPath, await readJson(req), access) });
6236
+ json2(res, 200, bundle);
6237
+ } else if (path && method === "GET") json2(res, 200, { skill: skills.get(decodedPath, access) });
6238
+ else if (path && method === "PUT") json2(res, 200, { skill: skills.update(decodedPath, await readJson(req), access) });
5158
6239
  else throw new WebRequestError("Method not allowed.", 405);
5159
6240
  return;
5160
6241
  }
@@ -5162,34 +6243,34 @@ function registerDashboardCommand(program) {
5162
6243
  if (engagementRoute) {
5163
6244
  const member = teamAuth.resolveSession(req);
5164
6245
  const engagementActor = member ? { userId: member.userId, displayName: member.displayName } : void 0;
5165
- const id = engagementRoute[1] ? decodeURIComponent(engagementRoute[1]) : void 0;
6246
+ const id2 = engagementRoute[1] ? decodeURIComponent(engagementRoute[1]) : void 0;
5166
6247
  if (engagementRoute[2]) {
5167
6248
  if (req.method !== "GET") throw new WebRequestError("Use GET to read an engagement report.", 405);
5168
6249
  const format = requestUrl.searchParams.get("format") ?? "json";
5169
6250
  if (format !== "json" && format !== "markdown") throw new WebRequestError("Use json or markdown for an engagement report.", 400);
5170
- const report = engagements.report(id);
6251
+ const report = engagements.report(id2);
5171
6252
  if (format === "json") {
5172
- res.setHeader("Content-Disposition", `attachment; filename="engagement-${id}.json"`);
5173
- json(res, 200, report);
6253
+ res.setHeader("Content-Disposition", `attachment; filename="engagement-${id2}.json"`);
6254
+ json2(res, 200, report);
5174
6255
  } else {
5175
- res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": `attachment; filename="engagement-${id}.md"`, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
6256
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": `attachment; filename="engagement-${id2}.md"`, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
5176
6257
  res.end(engagementMarkdown(report));
5177
6258
  }
5178
- } else if (req.method === "GET") json(res, 200, id ? { engagement: engagements.get(id) } : { engagements: engagements.list() });
5179
- else if (req.method === "POST" && !id) {
6259
+ } else if (req.method === "GET") json2(res, 200, id2 ? { engagement: engagements.get(id2) } : { engagements: engagements.list() });
6260
+ else if (req.method === "POST" && !id2) {
5180
6261
  const engagement = engagements.create(await readJson(req), engagementActor);
5181
6262
  collaboration?.notifyChanged("report", engagement.id);
5182
- json(res, 201, { engagement });
5183
- } else if (req.method === "PATCH" && id) {
5184
- const engagement = engagements.update(id, await readJson(req), engagementActor);
6263
+ json2(res, 201, { engagement });
6264
+ } else if (req.method === "PATCH" && id2) {
6265
+ const engagement = engagements.update(id2, await readJson(req), engagementActor);
5185
6266
  collaboration?.notifyChanged("report", engagement.id);
5186
- json(res, 200, { engagement });
6267
+ json2(res, 200, { engagement });
5187
6268
  } else throw new WebRequestError("Method not allowed.", 405);
5188
6269
  return;
5189
6270
  }
5190
6271
  if (requestUrl.pathname === "/api/workers/hosts") {
5191
6272
  if (req.method !== "GET") throw new WebRequestError("Method not allowed.", 405);
5192
- json(res, 200, { hosts: discoverSshHosts() });
6273
+ json2(res, 200, { hosts: discoverSshHosts() });
5193
6274
  return;
5194
6275
  }
5195
6276
  if (requestUrl.pathname === "/api/workers/managed") {
@@ -5202,7 +6283,15 @@ function registerDashboardCommand(program) {
5202
6283
  return [];
5203
6284
  }
5204
6285
  });
5205
- json(res, 200, { workers });
6286
+ json2(res, 200, { workers });
6287
+ return;
6288
+ }
6289
+ if (requestUrl.pathname === "/api/control/repair-worker") {
6290
+ if (req.method !== "POST") throw new WebRequestError("Use POST to reconnect a host.", 405);
6291
+ const body = external_exports.object({ backendId: BackendDescriptorSchema.shape.id }).strict().parse(await readJson(req));
6292
+ const repaired = await repairManagedHost(body.backendId);
6293
+ backends.reload();
6294
+ json2(res, 200, repaired);
5206
6295
  return;
5207
6296
  }
5208
6297
  const workerStop = requestUrl.pathname.match(/^\/api\/workers\/([^/]+)\/stop$/);
@@ -5224,7 +6313,7 @@ function registerDashboardCommand(program) {
5224
6313
  }
5225
6314
  });
5226
6315
  });
5227
- json(res, 200, result);
6316
+ json2(res, 200, result);
5228
6317
  return;
5229
6318
  }
5230
6319
  if (requestUrl.pathname === "/api/workers/deploy") {
@@ -5257,7 +6346,7 @@ function registerDashboardCommand(program) {
5257
6346
  });
5258
6347
  });
5259
6348
  backends.reload();
5260
- json(res, 202, result);
6349
+ json2(res, 202, result);
5261
6350
  return;
5262
6351
  }
5263
6352
  if (requestUrl.pathname === "/api/control/launch-run") {
@@ -5267,15 +6356,15 @@ function registerDashboardCommand(program) {
5267
6356
  if (body.ensureDaemon) throw new WebRequestError("Engine runs do not require a separate orchestration daemon. Configure a workflow schedule for recurring work.", 400);
5268
6357
  const session = await engine.invoke("create_session", { config: { target: body.target, title: `Assessment: ${body.target}` } });
5269
6358
  const execution = await engine.invoke("start_assessment", { sessionId: session.id, target: body.target, plan: { goal: "unknown-vulnerabilities", depth: body.depth ?? "default", runCount: 1, executionMode: "sequential", timeCapMs, costCapUsd } });
5270
- json(res, 202, { ok: true, engineOwned: true, sessionId: session.id, runId: execution.id, jobId: execution.jobId });
6359
+ json2(res, 202, { ok: true, engineOwned: true, sessionId: session.id, runId: execution.id, jobId: execution.jobId });
5271
6360
  return;
5272
6361
  }
5273
6362
  if (requestUrl.pathname === "/api/workflow-engine/call") {
5274
6363
  if (!engine) throw new WebRequestError("This engine does not admit persistent workflow calls.", 403);
5275
6364
  if (req.method !== "POST") throw new WebRequestError("Method not allowed.", 405);
5276
- const input = external_exports.object({ name: external_exports.string().min(1).max(128), args: external_exports.record(external_exports.unknown()) }).strict().parse(await readJson(req));
6365
+ const input = external_exports.object({ name: external_exports.string().min(1).max(128), args: external_exports.record(external_exports.string(), external_exports.unknown()) }).strict().parse(await readJson(req));
5277
6366
  if (!engineRequest && typeof input.args.sessionId === "string") assertTeamConsoleMutation(req, input.args.sessionId, teamAuth, collaboration, consoleGateway);
5278
- json(res, 200, await engine.invoke(input.name, input.args));
6367
+ json2(res, 200, await engine.invoke(input.name, input.args));
5279
6368
  return;
5280
6369
  }
5281
6370
  const resumeScan = requestUrl.pathname.match(/^\/api\/scans\/([^/]+)\/resume$/);
@@ -5283,14 +6372,14 @@ function registerDashboardCommand(program) {
5283
6372
  if (req.method !== "POST") throw new WebRequestError("Use POST to resume a scan.", 405);
5284
6373
  const request = ScanResumeRequestSchema.parse(await readJson(req));
5285
6374
  const result = await workflows.resumeScan(decodeURIComponent(resumeScan[1]), { ...request, timeCapMs: Math.min(request.timeCapMs ?? timeCapMs, timeCapMs), costCapUsd: Math.min(request.costCapUsd ?? costCapUsd, costCapUsd) });
5286
- json(res, result.status, result.data);
6375
+ json2(res, result.status, result.data);
5287
6376
  return;
5288
6377
  }
5289
6378
  if (/^\/api\/findings\/[^/]+\/impact-assessment$/.test(requestUrl.pathname)) {
5290
6379
  const input = req.method === "POST" || req.method === "DELETE" ? await readJson(req) : void 0;
5291
6380
  const result = handleFindingImpactRequest(requestUrl.pathname, req.method ?? "GET", input, { dbPath: opts.dbPath });
5292
6381
  if (result) {
5293
- json(res, result.status, result.data);
6382
+ json2(res, result.status, result.data);
5294
6383
  return;
5295
6384
  }
5296
6385
  }
@@ -5309,7 +6398,7 @@ function registerDashboardCommand(program) {
5309
6398
  const consoleHandled = await handleWebConsoleApiRequest(req, res, requestUrl, consoleGateway, operator, workflows, triggers, github, engineRequest ? void 0 : { auth: teamAuth, collaboration });
5310
6399
  if (consoleHandled) return;
5311
6400
  const handled = await handleApiRequest(req, res, requestUrl.pathname, opts.dbPath, controlToken, engineBearer);
5312
- if (!handled) json(res, 404, { error: "Not found" });
6401
+ if (!handled) json2(res, 404, { error: "Not found" });
5313
6402
  return;
5314
6403
  }
5315
6404
  if (devUrl) {
@@ -5330,12 +6419,12 @@ function registerDashboardCommand(program) {
5330
6419
  return;
5331
6420
  }
5332
6421
  if (extname(requestUrl.pathname)) {
5333
- json(res, 404, { error: "Asset not found" });
6422
+ json2(res, 404, { error: "Asset not found" });
5334
6423
  return;
5335
6424
  }
5336
6425
  sendFile(res, join7(assetDir, "index.html"), controlToken);
5337
6426
  } catch (err) {
5338
- json(res, errorStatusCode(err), { error: err instanceof Error ? err.message : "Web application request failed." });
6427
+ json2(res, errorStatusCode(err), { error: err instanceof Error ? err.message : "Web application request failed." });
5339
6428
  }
5340
6429
  });
5341
6430
  let dashboardAssetsCleaned = false;
@@ -5383,19 +6472,31 @@ function registerDashboardCommand(program) {
5383
6472
  const shutdown = () => {
5384
6473
  if (shuttingDown) return;
5385
6474
  shuttingDown = true;
5386
- backends.dispose();
5387
- void Promise.resolve(engine?.dispose()).then(() => triggers.dispose()).then(() => consoleGateway.closeAll()).then(() => workflows.dispose()).finally(() => {
6475
+ stopHostReconnect?.();
6476
+ hostRepairAbort.abort();
6477
+ services.remoteProviderRelays.dispose();
6478
+ void localProviderSessions.dispose().then(() => {
6479
+ modelBackends.dispose();
6480
+ backends.dispose();
6481
+ return engine?.dispose();
6482
+ }).then(() => triggers.dispose()).then(() => consoleGateway.closeAll()).then(() => workflows.dispose()).finally(() => {
5388
6483
  operator.dispose();
5389
6484
  github.dispose();
6485
+ for (const response of activeResponses) response.end();
5390
6486
  server.close(() => {
5391
6487
  cleanupDashboardAssets();
5392
6488
  process.exit(0);
5393
6489
  });
6490
+ server.closeAllConnections();
5394
6491
  });
5395
6492
  };
5396
6493
  process.once("SIGINT", shutdown);
5397
6494
  process.once("SIGTERM", shutdown);
5398
6495
  } catch (error2) {
6496
+ stopHostReconnect?.();
6497
+ hostRepairAbort.abort();
6498
+ await localProviderSessions.dispose();
6499
+ modelBackends.dispose();
5399
6500
  backends.dispose();
5400
6501
  await startupTriggers?.dispose().catch(() => {
5401
6502
  });