0sec-cli 0.24.1 → 0.26.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 (246) hide show
  1. package/0.js +67 -62
  2. package/README.md +121 -82
  3. package/chunks/{adapt-loop-L77BWMNE.js → adapt-loop-P4HKONB5.js} +6 -5
  4. package/chunks/{artifact-scraper-BV67RAP5.js → artifact-scraper-IIRJNYKT.js} +4 -3
  5. package/chunks/{assumption-mining-RPVR77A3.js → assumption-mining-YA6VJ2V6.js} +22 -20
  6. package/chunks/{backend-connections-ACIXNYZN.js → backend-connections-64PKYKL7.js} +5 -4
  7. package/chunks/chunk-36J5ZLFE.js +18729 -0
  8. package/chunks/{chunk-ZM6ANQZJ.js → chunk-3MWQNNJU.js} +16180 -34467
  9. package/chunks/{chunk-GV5WXDHY.js → chunk-3VQ5EZKG.js} +11 -9
  10. package/chunks/chunk-5XOYTAOK.js +1546 -0
  11. package/chunks/{chunk-UJE4WOQS.js → chunk-6GV5G6U3.js} +1 -1
  12. package/chunks/{chunk-X2ZMSRTP.js → chunk-6SNOKNCS.js} +333 -3
  13. package/chunks/{chunk-BMDG6T7D.js → chunk-72PU46MS.js} +178 -133
  14. package/chunks/{chunk-KCA35IL3.js → chunk-7PUJI64A.js} +11 -9
  15. package/chunks/chunk-A7G6S5RA.js +90 -0
  16. package/chunks/chunk-ABKUI3QB.js +196 -0
  17. package/chunks/{chunk-5ETI27Q3.js → chunk-AFJ2UMPR.js} +3 -3
  18. package/chunks/{chunk-XA2IBHPB.js → chunk-B7XL6RON.js} +1 -1
  19. package/chunks/{chunk-TVS6WVSK.js → chunk-BM6EYP2D.js} +3 -3
  20. package/chunks/{chunk-7UHAXEOV.js → chunk-D4M5WURF.js} +226 -4
  21. package/chunks/{chunk-K74DRDC5.js → chunk-EHNVP6QB.js} +1 -1
  22. package/chunks/{chunk-I43YYHDU.js → chunk-FBZBJYZS.js} +5 -50
  23. package/chunks/{chunk-GXXX7AGH.js → chunk-GYEYTNHK.js} +144 -14
  24. package/chunks/{chunk-IJ6UPW3H.js → chunk-HS2NQ7K6.js} +393 -58
  25. package/chunks/chunk-HUZ2EC2R.js +196 -0
  26. package/chunks/{chunk-SU4IHQQI.js → chunk-IUUNIJPM.js} +1 -1
  27. package/chunks/chunk-JGDA6EIJ.js +49 -0
  28. package/chunks/{chunk-CJRZSWII.js → chunk-K3PTYN43.js} +3 -3
  29. package/chunks/{chunk-2NBZZFRG.js → chunk-KCABBZHN.js} +8 -10
  30. package/chunks/{chunk-3LPBJMHB.js → chunk-LRD5N2TQ.js} +1 -1
  31. package/chunks/{chunk-SQCBHFWN.js → chunk-LWMEJYCP.js} +2 -2
  32. package/chunks/chunk-MNK373HF.js +57 -0
  33. package/chunks/{chunk-R2XBH476.js → chunk-MOEVVXML.js} +2 -2
  34. package/chunks/{chunk-F2P25DJY.js → chunk-MOGWEHSC.js} +1448 -1050
  35. package/chunks/{chunk-6XMFCVJS.js → chunk-N7YCONYR.js} +1 -1
  36. package/chunks/{chunk-3WUPCZGF.js → chunk-NA3FKCI2.js} +1 -1
  37. package/chunks/{chunk-S2GITRD5.js → chunk-NABVDAAO.js} +2 -2
  38. package/chunks/{chunk-SZDD77KX.js → chunk-NNMOTHKC.js} +6 -10
  39. package/chunks/{chunk-LMP2T4G2.js → chunk-OILVEROI.js} +22 -172
  40. package/chunks/{chunk-YERUDEXT.js → chunk-PAE7ZAV6.js} +2780 -1255
  41. package/chunks/{chunk-NBV4I237.js → chunk-PAMJEXE2.js} +1 -1
  42. package/chunks/{chunk-PJU24AKR.js → chunk-R7VWEY45.js} +337 -257
  43. package/chunks/{chunk-QNRYOJEB.js → chunk-RZZ6VPRY.js} +1 -1
  44. package/chunks/{chunk-WUPN3D37.js → chunk-S4DFH2FJ.js} +26 -1
  45. package/chunks/{chunk-5WAHHZBF.js → chunk-SIR7XW2S.js} +61910 -30777
  46. package/chunks/{chunk-BD7WULFS.js → chunk-SSPP64FF.js} +3 -2
  47. package/chunks/{chunk-BOBIAHKA.js → chunk-STPHTPSD.js} +3 -333
  48. package/chunks/{chunk-Q7JKIKKF.js → chunk-UGCTPKJG.js} +89 -17
  49. package/chunks/{chunk-5PFRE522.js → chunk-ULRLUXSS.js} +1 -1
  50. package/chunks/{chunk-GZA7RGK7.js → chunk-UQS7XCBV.js} +2085 -1093
  51. package/chunks/{chunk-X2IPO5JO.js → chunk-V2KJQVYE.js} +2676 -2959
  52. package/chunks/{chunk-CGBDEST2.js → chunk-VJBEB2K6.js} +1 -1
  53. package/chunks/{chunk-ILRMLA6J.js → chunk-VWE36MUN.js} +1 -1
  54. package/chunks/{chunk-RURJYCWY.js → chunk-WNWDQ2TO.js} +1 -1
  55. package/chunks/{chunk-GTED3FSZ.js → chunk-WPJMA5AK.js} +4 -99
  56. package/chunks/{chunk-5NFIY2SJ.js → chunk-XJ2TBEV7.js} +3 -3
  57. package/chunks/{chunk-PVJUPIQG.js → chunk-XOVYV7TT.js} +6 -6
  58. package/chunks/{chunk-IXT64DV5.js → chunk-XPDJE537.js} +97 -1
  59. package/chunks/{chunk-YKWARO3T.js → chunk-YGYJ2FTH.js} +95 -21
  60. package/chunks/{chunk-JWSOUKFF.js → chunk-YJGPPPUB.js} +66 -21
  61. package/chunks/chunk-YP2DHYNI.js +650 -0
  62. package/chunks/{chunk-RVJVM5L4.js → chunk-Z2VLJEQJ.js} +5 -5
  63. package/chunks/chunk-ZPVSDB63.js +3839 -0
  64. package/chunks/{codex-models-6XVNFRXZ.js → codex-models-CI22KIHY.js} +9 -8
  65. package/chunks/{commands-FRPOFIO4.js → commands-C2WRSCNB.js} +909 -610
  66. package/chunks/dashboard-XO2X2SPD.js +75 -0
  67. package/chunks/{db-BXJNWUYJ.js → db-FU3OIGQK.js} +5 -4
  68. package/chunks/{deep-review-GSJEBLCG.js → deep-review-5AL6QYTK.js} +37 -38
  69. package/chunks/{disclose-OU6Z6GVT.js → disclose-YPV5KIUU.js} +6 -5
  70. package/chunks/{dist-M2X367VW.js → dist-JAZWL3SC.js} +3 -2
  71. package/chunks/{dist-TQMHMSBZ.js → dist-ONIRKDH6.js} +376 -333
  72. package/chunks/{dist-RKLQIXDT.js → dist-QLMPTFT7.js} +4 -3
  73. package/chunks/{dist-7KSYDDSG.js → dist-QXJKD743.js} +5 -2
  74. package/chunks/{dist-LNXGATR2.js → dist-WNAOCQGA.js} +5 -4
  75. package/chunks/eval-runner-D3RUCU3R.js +38 -0
  76. package/chunks/{exploit-agent-Y2P4GFIY.js → exploit-agent-4E4HXKUA.js} +1 -1
  77. package/chunks/{exploit-autoclimb-YRZ3TMS2.js → exploit-autoclimb-5GRXI6VQ.js} +1 -1
  78. package/chunks/{exploit-climb-4LEY4SDW.js → exploit-climb-AHQEJ2I2.js} +4 -3
  79. package/chunks/{fix-RQSIG75B.js → fix-CHD7LXDS.js} +9 -8
  80. package/chunks/{harness-3NVC3DDY.js → harness-E63KDHPG.js} +5 -4
  81. package/chunks/{http-conformance-U5F6GKYG.js → http-conformance-AXMUOE6C.js} +3 -2
  82. package/chunks/hunt-scan-ME2Z4CTC.js +43 -0
  83. package/chunks/{kernel-primitive-T47V3RD3.js → kernel-primitive-S566GXWP.js} +4 -3
  84. package/chunks/{kernel-vm-runner-AEL5E3SV.js → kernel-vm-runner-KREBY4DC.js} +4 -3
  85. package/chunks/{local-engine-ZCW4YC6E.js → local-engine-ZRTZVSSX.js} +7 -6
  86. package/chunks/managed-operation-ZMLLRCCQ.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-UV3Z4Z2F.js} +31 -16
  89. package/chunks/operator-services-JOCREEBA.js +79 -0
  90. package/chunks/orchestrate-ZF6ZTSIQ.js +62 -0
  91. package/chunks/{prepare-C3KGMBXT.js → prepare-PLDVGLDX.js} +7 -6
  92. package/chunks/{provider-model-discovery-QGUGL3XW.js → provider-model-discovery-3X6DON3Y.js} +7 -2
  93. package/chunks/{remote-workflow-runtime-DB52SINR.js → remote-workflow-runtime-GOI4TYP5.js} +4 -3
  94. package/chunks/routing-WCQLO5SP.js +61 -0
  95. package/chunks/{runtime-AUBLQUOH.js → runtime-6ZIXFTW5.js} +4 -3
  96. package/chunks/{runtime-U66JNSAY.js → runtime-TVH5ORUK.js} +13 -8
  97. package/chunks/{scan-stream-34F3LTPV.js → scan-stream-HJVTWZ44.js} +6 -4
  98. package/chunks/{scope-HSTG63TQ.js → scope-RCYXVGJR.js} +4 -3
  99. package/chunks/service-plugins-TN6KDX4L.js +63 -0
  100. package/chunks/{session-store-MKAAP3WL.js → session-store-6QR3CVUA.js} +8 -5
  101. package/chunks/{variant-candidates-LG767HWH.js → variant-candidates-SGLKPPHI.js} +9 -8
  102. package/chunks/{web-recon-prepass-NJP6J3E4.js → web-recon-prepass-GO7CTMVQ.js} +7 -6
  103. package/chunks/{workflow-runtime-AR627I26.js → workflow-runtime-CQTDASFA.js} +50 -49
  104. package/dashboard/assets/{abnfDiagram-VCTEODGH-Cq149Jpk.js → abnfDiagram-VCTEODGH-DDZsU70-.js} +1 -1
  105. package/dashboard/assets/{arc-CpP-3TRW.js → arc-iDXNTSL6.js} +1 -1
  106. package/dashboard/assets/architecture-7GRP2DOG-DJcY9RFg.js +1 -0
  107. package/dashboard/assets/architectureDiagram-5GKGNRK7-d0ZuOniQ.js +36 -0
  108. package/dashboard/assets/{blockDiagram-I7D4REHJ-CZYkRRDi.js → blockDiagram-I7D4REHJ-B-EWfMy1.js} +2 -2
  109. package/dashboard/assets/{c4Diagram-7LVT6UL2-CV7Iu3_Z.js → c4Diagram-7LVT6UL2-KcZRuXZC.js} +1 -1
  110. package/dashboard/assets/channel-CeWMIm0w.js +1 -0
  111. package/dashboard/assets/{chunk-4HAMMTFA-PKCyW4m-.js → chunk-4HAMMTFA-DXWulBlo.js} +1 -1
  112. package/dashboard/assets/{chunk-75Z2AOVW-C5MlFTc_.js → chunk-75Z2AOVW-RiLZItrz.js} +1 -1
  113. package/dashboard/assets/chunk-DU6HZSFF-MBcaIyTX.js +127 -0
  114. package/dashboard/assets/{chunk-F27PBJKO-DTAok0qM.js → chunk-F27PBJKO-kLWCBX_W.js} +1 -1
  115. package/dashboard/assets/{chunk-FOHPRMQF-DHwB1DNv.js → chunk-FOHPRMQF-COOliuUh.js} +14 -14
  116. package/dashboard/assets/{chunk-GMAD6QVW-CPsr2F33.js → chunk-GMAD6QVW-BeQeSjpW.js} +7 -7
  117. package/dashboard/assets/{chunk-GVQU2GXP-C2AXGf9A.js → chunk-GVQU2GXP-CjCMlOIB.js} +1 -1
  118. package/dashboard/assets/{chunk-IMKFNOWR-DMG6yD_O.js → chunk-IMKFNOWR-BwNst53D.js} +1 -1
  119. package/dashboard/assets/{chunk-L3NEJ4N5-BSvwdbXx.js → chunk-L3NEJ4N5-CHXWQrkL.js} +1 -1
  120. package/dashboard/assets/{chunk-OSK3NFVY-CfB8V7FU.js → chunk-OSK3NFVY-B2vd19Ed.js} +1 -1
  121. package/dashboard/assets/{chunk-P2QGCYS3-CvmZvq6n.js → chunk-P2QGCYS3-Dk7Fyh6n.js} +1 -1
  122. package/dashboard/assets/{chunk-POPQ4Y6H-D_TvwDxL.js → chunk-POPQ4Y6H-CPAgOvYe.js} +1 -1
  123. package/dashboard/assets/{chunk-PWAF6VOD-Da5GnuOY.js → chunk-PWAF6VOD-BotyfMha.js} +1 -1
  124. package/dashboard/assets/{chunk-SHT3W25Y-CcsMJpR-.js → chunk-SHT3W25Y-Buz5gLRP.js} +1 -1
  125. package/dashboard/assets/{chunk-SVP7TREG-COojjieJ.js → chunk-SVP7TREG-_rpYEZcs.js} +1 -1
  126. package/dashboard/assets/{chunk-TICWLB2K-nBAmJwx6.js → chunk-TICWLB2K-DOJxPoec.js} +2 -2
  127. package/dashboard/assets/{chunk-XXDRQBXY-r5HyhWDv.js → chunk-XXDRQBXY-EXxKJbAs.js} +1 -1
  128. package/dashboard/assets/classDiagram-ZZMXUADV-DV_h1NoS.js +1 -0
  129. package/dashboard/assets/classDiagram-v2-VYDZK3BY-DV_h1NoS.js +1 -0
  130. package/dashboard/assets/common-VLJRx-Jq.js +5 -0
  131. package/dashboard/assets/cose-bilkent-JH36ORCC-B1SVC57o.js +1 -0
  132. package/dashboard/assets/cynefin-OW5HDTMX-BzhRorj4.js +1 -0
  133. package/dashboard/assets/{cynefinDiagram-5FMLGOSQ-xodkuQtW.js → cynefinDiagram-5FMLGOSQ-JXzRmldX.js} +1 -1
  134. package/dashboard/assets/cytoscape.esm-BbBREfsg.js +321 -0
  135. package/dashboard/assets/dagre-D7JM2Yhv.js +1 -0
  136. package/dashboard/assets/dagre-GXQ25YYZ-1LZ-X0-Z.js +4 -0
  137. package/dashboard/assets/{diagram-S7CK7UJ4-DNoTMlCO.js → diagram-S7CK7UJ4-C_-J_LQ4.js} +2 -2
  138. package/dashboard/assets/{diagram-UQ7AKVKN-COccuyew.js → diagram-UQ7AKVKN-CnxL1k8g.js} +1 -1
  139. package/dashboard/assets/{diagram-VSXAHHWV-g6D1tWRb.js → diagram-VSXAHHWV-WzSb3aby.js} +1 -1
  140. package/dashboard/assets/{diagram-VX7I27RA-DhO4eVIC.js → diagram-VX7I27RA-DKhrMInE.js} +1 -1
  141. package/dashboard/assets/{diagram-Z3DM3KII-DeOQFexd.js → diagram-Z3DM3KII-CDhI2WGP.js} +1 -1
  142. package/dashboard/assets/{dist-BQYeOQg3.js → dist-B0bfBMeP.js} +1 -1
  143. package/dashboard/assets/{ebnfDiagram-PWID7BFC-MtkX2feM.js → ebnfDiagram-PWID7BFC-iL5NQ1PE.js} +1 -1
  144. package/dashboard/assets/{erDiagram-RLTQ6QDP-DIWSsoYZ.js → erDiagram-RLTQ6QDP-RK2Voa5O.js} +1 -1
  145. package/dashboard/assets/eventmodeling-NTZA5JFV-WgE7OJBb.js +1 -0
  146. package/dashboard/assets/flowDiagram-HODETNUW-B9YcQ0Aq.js +1 -0
  147. package/dashboard/assets/{ganttDiagram-EL5Y4UJY-DKggkJe0.js → ganttDiagram-EL5Y4UJY-D8CJXHRF.js} +1 -1
  148. package/dashboard/assets/gitGraph-4MIJSDKK-BuZ9Lg04.js +1 -0
  149. package/dashboard/assets/{gitGraphDiagram-WWUBYQGX-r_iR8Qzo.js → gitGraphDiagram-WWUBYQGX-LEJMh54j.js} +1 -1
  150. package/dashboard/assets/info-A6RAGUB7-BL5k3pev.js +1 -0
  151. package/dashboard/assets/{infoDiagram-27XIBGKW-ClQk7UyQ.js → infoDiagram-27XIBGKW-LJvntUnZ.js} +1 -1
  152. package/dashboard/assets/{ishikawaDiagram-5VMMS53U-tR7Vziog.js → ishikawaDiagram-5VMMS53U-BYTYgKCG.js} +2 -2
  153. package/dashboard/assets/{journeyDiagram-3NMN7TZE-Cpb9IqKL.js → journeyDiagram-3NMN7TZE-G25qgQgO.js} +1 -1
  154. package/dashboard/assets/{kanban-definition-UXKFOSKX-BzUuKbA9.js → kanban-definition-UXKFOSKX-Bk3uLl1i.js} +1 -1
  155. package/dashboard/assets/{katex-BdBmVSus.js → katex-nLxf_RlZ.js} +2 -2
  156. package/dashboard/assets/{line-BHY_tSHu.js → line-i4qHBaWO.js} +1 -1
  157. package/dashboard/assets/linear-DmrqRsKG.js +1 -0
  158. package/dashboard/assets/mermaid-parser.core-BxbKr4QF.js +7 -0
  159. package/dashboard/assets/{mermaid.core-D_QsUPU8.js → mermaid.core-Cltee6FS.js} +9 -9
  160. package/dashboard/assets/{mindmap-definition-YA3MSWOX-CtfLTPPJ.js → mindmap-definition-YA3MSWOX-DkzO2JgS.js} +1 -1
  161. package/dashboard/assets/operations-B4_timQX.css +1 -0
  162. package/dashboard/assets/operations-DwppJviL.js +199 -0
  163. package/dashboard/assets/packet-AYTQ26CC-uZQUKnrE.js +1 -0
  164. package/dashboard/assets/{pegDiagram-XKGWAZYB-C1Dclbzj.js → pegDiagram-XKGWAZYB-CrOEi00T.js} +1 -1
  165. package/dashboard/assets/pie-WAS4IAKB-Dn9_tpQT.js +1 -0
  166. package/dashboard/assets/{pieDiagram-E7YTZNPT-Cd7XwYo5.js → pieDiagram-E7YTZNPT-D-niUkzc.js} +1 -1
  167. package/dashboard/assets/{quadrantDiagram-AXDQQJYC-CaS-GQ4X.js → quadrantDiagram-AXDQQJYC-CJOTiG5V.js} +1 -1
  168. package/dashboard/assets/radar-RG4KPBEZ-DIIVz4e8.js +1 -0
  169. package/dashboard/assets/railroad-74A4TZTK-1kiqDEID.js +1 -0
  170. package/dashboard/assets/railroad-abnf-HS5TGJTU-DK6rQPZR.js +1 -0
  171. package/dashboard/assets/railroad-ebnf-LZEXJU2U-C6cOgYW4.js +1 -0
  172. package/dashboard/assets/railroad-peg-WCYAUIDC-CQ3ptKef.js +1 -0
  173. package/dashboard/assets/{railroadDiagram-O6MQD6OU-BeesTrqn.js → railroadDiagram-O6MQD6OU-nEfkpa-m.js} +1 -1
  174. package/dashboard/assets/{requirementDiagram-BXWQKSXE-IjyK0DhW.js → requirementDiagram-BXWQKSXE-CdFMY8tG.js} +1 -1
  175. package/dashboard/assets/{sankeyDiagram-P5KCCOFB-DmIluQqo.js → sankeyDiagram-P5KCCOFB-C0KxrFYv.js} +1 -1
  176. package/dashboard/assets/{sequenceDiagram-WJ2MYXX4-CUp8ZjTc.js → sequenceDiagram-WJ2MYXX4-DeZ5ATiu.js} +2 -2
  177. package/dashboard/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  178. package/dashboard/assets/src-Cg-chcoE.js +1 -0
  179. package/dashboard/assets/{stateDiagram-D77RDMKH-Dy9TQb8P.js → stateDiagram-D77RDMKH-B9MwWL5B.js} +1 -1
  180. package/dashboard/assets/stateDiagram-v2-MP3YSRHH-SucFndhx.js +1 -0
  181. package/dashboard/assets/swimlanes-42K2YHIH-DB--fwQE.js +1 -0
  182. package/dashboard/assets/swimlanesDiagram-VR7AAH4N-BzH-rIaK.js +8 -0
  183. package/dashboard/assets/{timeline-definition-24CTP7MA-BGdXW-iY.js → timeline-definition-24CTP7MA-DLkkTbWb.js} +1 -1
  184. package/dashboard/assets/treeView-Q6P3EWNA-XkZ9rl9H.js +1 -0
  185. package/dashboard/assets/treemap-WGGIJYW6-JCtVKxCE.js +1 -0
  186. package/dashboard/assets/{vennDiagram-4TSXK5OY-DzKWHJsl.js → vennDiagram-4TSXK5OY-DEb-x29Z.js} +1 -1
  187. package/dashboard/assets/wardley-WFR3VGLG-DhI7ROY3.js +1 -0
  188. package/dashboard/assets/{wardleyDiagram-VM6X3IG4-DGsjYmNJ.js → wardleyDiagram-VM6X3IG4-KNckudc5.js} +1 -1
  189. package/dashboard/assets/{xychartDiagram-S5SC5T6Z-C7_T_tWt.js → xychartDiagram-S5SC5T6Z-D4c0ynCk.js} +1 -1
  190. package/dashboard/index.html +13 -2
  191. package/package.json +2 -5
  192. package/worker-assets/demo-target/README.md +7 -3
  193. package/chunks/chunk-2XGBEH53.js +0 -4304
  194. package/chunks/chunk-4ROZFLSR.js +0 -279
  195. package/chunks/chunk-56UTQKXO.js +0 -56
  196. package/chunks/chunk-BPU6M42R.js +0 -77
  197. package/chunks/chunk-H5QURBBK.js +0 -1123
  198. package/chunks/chunk-IIBNQTOK.js +0 -1532
  199. package/chunks/chunk-NJLKFC66.js +0 -4790
  200. package/chunks/chunk-OOMBOLAF.js +0 -295
  201. package/chunks/chunk-VIT5ALXF.js +0 -4054
  202. package/chunks/chunk-VU7V2SDW.js +0 -284
  203. package/chunks/chunk-W624J7NF.js +0 -117
  204. package/chunks/chunk-YSQWZ67M.js +0 -6027
  205. package/chunks/dashboard-5HDSNBAU.js +0 -76
  206. package/chunks/eval-runner-A2DIGVP7.js +0 -31
  207. package/chunks/hunt-scan-W246VWJC.js +0 -40
  208. package/chunks/managed-operation-6R2QPPW5.js +0 -60
  209. package/chunks/orchestrate-OBS37FWQ.js +0 -63
  210. package/chunks/run-MU3MFL2Z.js +0 -28242
  211. package/chunks/service-plugins-5B23PWXC.js +0 -64
  212. package/dashboard/assets/architecture-7GRP2DOG-z5EHYkyH.js +0 -1
  213. package/dashboard/assets/architectureDiagram-5GKGNRK7-ewtXpjR_.js +0 -36
  214. package/dashboard/assets/channel-CuYjfiC5.js +0 -1
  215. package/dashboard/assets/chunk-DU6HZSFF-3M37ulcO.js +0 -127
  216. package/dashboard/assets/classDiagram-ZZMXUADV-DCuOrWUV.js +0 -1
  217. package/dashboard/assets/classDiagram-v2-VYDZK3BY-DCuOrWUV.js +0 -1
  218. package/dashboard/assets/common-C6snVPsk.js +0 -5
  219. package/dashboard/assets/cose-bilkent-JH36ORCC-BLGcaZHk.js +0 -1
  220. package/dashboard/assets/cynefin-OW5HDTMX-DbZMK00I.js +0 -1
  221. package/dashboard/assets/cytoscape.esm-CGd-uY3x.js +0 -321
  222. package/dashboard/assets/dagre-GXQ25YYZ-CRk9qfSG.js +0 -4
  223. package/dashboard/assets/dagre-PrKaheQc.js +0 -1
  224. package/dashboard/assets/eventmodeling-NTZA5JFV-D_3ez4Gv.js +0 -1
  225. package/dashboard/assets/flowDiagram-HODETNUW-D2boyYEd.js +0 -1
  226. package/dashboard/assets/gitGraph-4MIJSDKK-DFOQYwXc.js +0 -1
  227. package/dashboard/assets/info-A6RAGUB7-DeednLoB.js +0 -1
  228. package/dashboard/assets/linear-naZZ1pHk.js +0 -1
  229. package/dashboard/assets/mermaid-parser.core-CPvd4cEx.js +0 -7
  230. package/dashboard/assets/operations-BwCzhVnT.js +0 -130
  231. package/dashboard/assets/operations-QjKT0Hwi.css +0 -1
  232. package/dashboard/assets/packet-AYTQ26CC-DDQbkT9I.js +0 -1
  233. package/dashboard/assets/pie-WAS4IAKB-LpU4ULAs.js +0 -1
  234. package/dashboard/assets/radar-RG4KPBEZ-CcxW3NJm.js +0 -1
  235. package/dashboard/assets/railroad-74A4TZTK-9P5Rl8fP.js +0 -1
  236. package/dashboard/assets/railroad-abnf-HS5TGJTU-CSfpwjna.js +0 -1
  237. package/dashboard/assets/railroad-ebnf-LZEXJU2U-D6R5qzr-.js +0 -1
  238. package/dashboard/assets/railroad-peg-WCYAUIDC-DKEX7ITG.js +0 -1
  239. package/dashboard/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
  240. package/dashboard/assets/src-r61wDjcA.js +0 -1
  241. package/dashboard/assets/stateDiagram-v2-MP3YSRHH-CQb0ZtPr.js +0 -1
  242. package/dashboard/assets/swimlanes-42K2YHIH-CZuDV7V5.js +0 -1
  243. package/dashboard/assets/swimlanesDiagram-VR7AAH4N-BxgWMq8X.js +0 -8
  244. package/dashboard/assets/treeView-Q6P3EWNA-DlRrayjM.js +0 -1
  245. package/dashboard/assets/treemap-WGGIJYW6-C6UqLKfI.js +0 -1
  246. package/dashboard/assets/wardley-WFR3VGLG-thmyHKb0.js +0 -1
@@ -4,24 +4,23 @@ 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";
8
+ WorkflowEngineService,
9
+ buildFindingConsoleCommand,
10
+ createLocalConsoleSession,
11
+ findingFromRow,
12
+ loadFindingFocus
13
+ } from "./chunk-UQS7XCBV.js";
11
14
  import {
12
15
  assertLocalEngineAvailable,
13
16
  registerLocalEngine
14
- } from "./chunk-RVJVM5L4.js";
15
- import {
16
- findingFromRow as findingFromRow2
17
- } from "./chunk-W624J7NF.js";
17
+ } from "./chunk-Z2VLJEQJ.js";
18
18
  import {
19
- buildFindingConsoleCommand,
20
- createLocalConsoleSession,
19
+ WebOperatorServices,
20
+ createRemoteJWKSet,
21
21
  createSessionPluginHostManager,
22
- findingFromRow,
23
- loadFindingFocus
24
- } from "./chunk-2XGBEH53.js";
22
+ jwtVerify
23
+ } from "./chunk-ZPVSDB63.js";
25
24
  import {
26
25
  BackendConnectionRegistry,
27
26
  backendBearerFromEnv,
@@ -29,13 +28,11 @@ import {
29
28
  handleBackendConnectionRequest,
30
29
  loadManagedWorker,
31
30
  validateWorkerOptions
32
- } from "./chunk-Q7JKIKKF.js";
33
- import {
34
- Option
35
- } from "./chunk-WUPN3D37.js";
31
+ } from "./chunk-UGCTPKJG.js";
36
32
  import {
33
+ Option,
37
34
  presentationEventBus
38
- } from "./chunk-56UTQKXO.js";
35
+ } from "./chunk-S4DFH2FJ.js";
39
36
  import {
40
37
  source_default
41
38
  } from "./chunk-HLFS6WV2.js";
@@ -61,43 +58,47 @@ import {
61
58
  validateWorkbenchSourceLesson,
62
59
  workbenchConfigPath,
63
60
  workbenchNetworkEnabled
64
- } from "./chunk-X2IPO5JO.js";
61
+ } from "./chunk-V2KJQVYE.js";
65
62
  import {
66
63
  connectServicePlugins,
67
64
  createConsoleRuntime,
68
65
  exportReport,
69
66
  isReportExportFormat
70
- } from "./chunk-ZM6ANQZJ.js";
67
+ } from "./chunk-3MWQNNJU.js";
71
68
  import {
72
69
  HuntMemoryStore,
73
70
  TOOL_DEFINITIONS,
74
71
  discoverAgentSkills,
72
+ listBrowserViews,
75
73
  loadSkillRegistry,
74
+ parseImpactAssessment,
76
75
  readToolCallNames,
76
+ subscribeBrowserView,
77
77
  validateAgentSkillContent
78
- } from "./chunk-5WAHHZBF.js";
78
+ } from "./chunk-SIR7XW2S.js";
79
79
  import {
80
80
  SecurityWorkflowStore,
81
81
  WorkflowTriggerStore,
82
82
  findingStatuses,
83
83
  nextWorkflowTriggerFire,
84
84
  osecDB,
85
- resolveOsecDbPath
86
- } from "./chunk-BMDG6T7D.js";
85
+ resolveOsecDbPath,
86
+ restoreFindingReviewFields
87
+ } from "./chunk-72PU46MS.js";
87
88
  import {
88
89
  LlmApiRuntime
89
- } from "./chunk-IJ6UPW3H.js";
90
+ } from "./chunk-HS2NQ7K6.js";
90
91
  import {
91
92
  DEFAULT_WORKBENCH_BROKER_LIMITS,
92
93
  approveSmolvmWorkbenchImage,
93
94
  getSmolvmWorkbenchStatus,
94
95
  isAdmittedSmolvmWorkbench,
95
96
  resolveSmolvmRuntime
96
- } from "./chunk-PVJUPIQG.js";
97
+ } from "./chunk-XOVYV7TT.js";
97
98
  import {
98
99
  ScopePolicy,
99
100
  withScopeEnforcement
100
- } from "./chunk-QNRYOJEB.js";
101
+ } from "./chunk-RZZ6VPRY.js";
101
102
  import {
102
103
  BackendDescriptorSchema,
103
104
  BackendRequestIdSchema,
@@ -106,14 +107,14 @@ import {
106
107
  VERSION,
107
108
  createPresentationEvent,
108
109
  homeStateDir
109
- } from "./chunk-GXXX7AGH.js";
110
+ } from "./chunk-GYEYTNHK.js";
110
111
  import {
111
112
  external_exports
112
- } from "./chunk-VIT5ALXF.js";
113
+ } from "./chunk-5XOYTAOK.js";
113
114
 
114
115
  // packages/cli/src/commands/dashboard.ts
115
116
  import { createServer as createServer2 } from "node:http";
116
- import { createHash as createHash8, randomUUID as randomUUID7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
117
+ import { createHash as createHash8, randomUUID as randomUUID9, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
117
118
  import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
118
119
  import { existsSync as existsSync2, mkdirSync as mkdirSync6, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
119
120
  import { dirname as dirname2, extname, join as join7, resolve as resolve4, sep } from "node:path";
@@ -132,6 +133,44 @@ import { createServer } from "node:http";
132
133
  import { randomUUID } from "node:crypto";
133
134
  import { spawn } from "node:child_process";
134
135
  import { mkdir, writeFile } from "node:fs/promises";
136
+
137
+ // packages/cli/src/tui/findings-data.ts
138
+ function parseJsonColumn(raw) {
139
+ if (!raw) return void 0;
140
+ try {
141
+ return JSON.parse(raw);
142
+ } catch {
143
+ return void 0;
144
+ }
145
+ }
146
+ function findingFromRow2(row) {
147
+ const impactAssessment = parseJsonColumn(row.impactAssessment) === void 0 ? void 0 : parseImpactAssessment(row.impactAssessment) ?? void 0;
148
+ const record = {
149
+ id: row.id,
150
+ templateId: row.templateId,
151
+ title: row.title,
152
+ description: row.description,
153
+ severity: row.severity,
154
+ cvssScore: row.cvssScore ?? void 0,
155
+ cvssVector: row.cvssVector ?? void 0,
156
+ category: row.category,
157
+ status: row.status,
158
+ fingerprint: row.fingerprint ?? void 0,
159
+ triageStatus: row.triageStatus ?? void 0,
160
+ timestamp: row.timestamp,
161
+ evidence: {
162
+ request: row.evidenceRequest,
163
+ response: row.evidenceResponse,
164
+ analysis: row.evidenceAnalysis ?? void 0
165
+ },
166
+ verificationSpec: parseJsonColumn(row.verificationSpec),
167
+ impactAssessment,
168
+ ...restoreFindingReviewFields(row)
169
+ };
170
+ return record;
171
+ }
172
+
173
+ // packages/cli/src/workbench-console-guest.ts
135
174
  var CALLBACKS = ["requestScope", "requestLocalScope", "approveTool", "escalateScopedAudit", "askOperator"];
136
175
  var EVENTS = ["onHarnessUpdate", "onAssistantDelta", "onReasoningDelta", "onToolStart", "onToolResult", "onUsage", "onNotice", "onCompaction"];
137
176
  async function runGuest(cli) {
@@ -156,9 +195,9 @@ async function runGuest(cli) {
156
195
  let policy;
157
196
  const callback = (name, args) => {
158
197
  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 }));
198
+ const id2 = randomUUID();
199
+ emit({ type: "decision", id: id2, name, args });
200
+ return new Promise((resolve5, reject) => decisions.set(id2, { resolve: resolve5, reject }));
162
201
  };
163
202
  const server = createServer(async (request, response) => {
164
203
  if (request.method !== "POST" || request.url !== "/provider/request" || providers.size >= 4) {
@@ -175,12 +214,12 @@ async function runGuest(cli) {
175
214
  }
176
215
  const envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
177
216
  if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new Error("Invalid provider request");
178
- const id = randomUUID();
179
- providers.set(id, response);
217
+ const id2 = randomUUID();
218
+ providers.set(id2, response);
180
219
  response.once("close", () => {
181
- if (providers.delete(id)) emit({ type: "provider-cancel", id });
220
+ if (providers.delete(id2)) emit({ type: "provider-cancel", id: id2 });
182
221
  });
183
- emit({ type: "provider", id, envelope });
222
+ emit({ type: "provider", id: id2, envelope });
184
223
  } catch {
185
224
  response.writeHead(400).end("Invalid provider request");
186
225
  }
@@ -205,6 +244,7 @@ async function runGuest(cli) {
205
244
  tools: session.tools,
206
245
  messages: session.messages,
207
246
  autonomyMode: session.autonomyMode,
247
+ webSearchEnabled: session.webSearchEnabled,
208
248
  target: session.target,
209
249
  scope: session.scope?.raw,
210
250
  localScopePath: session.localScopePath,
@@ -212,7 +252,7 @@ async function runGuest(cli) {
212
252
  };
213
253
  await mkdir("/home/zero/.0/controller", { recursive: true, mode: 448 });
214
254
  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 });
255
+ emit({ type: "state", snapshot: { ...snapshot, ...checkpoint?.harnessRoot ? { checkpoint: void 0 } : {}, sourceContext: session.sourceContext } });
216
256
  const db = new osecDB("/home/zero/.0/controller.sqlite");
217
257
  try {
218
258
  const rows = db.getFindings(session.scanId);
@@ -373,6 +413,23 @@ async function runGuest(cli) {
373
413
  }
374
414
  });
375
415
  };
416
+ if (frame.callbacks.includes("skillRead")) callbacks.skillAuthoring = {
417
+ read: (id2) => callback("skillRead", [id2]),
418
+ create: (content) => callback("skillCreate", [content]),
419
+ update: (id2, input) => callback("skillSave", [id2, input]),
420
+ copy: (id2, input) => callback("skillCopy", [id2, input])
421
+ };
422
+ if (frame.callbacks.includes("skillEditorRead")) callbacks.skillEditor = { read: () => callback("skillEditorRead", []) };
423
+ if (frame.callbacks.includes("workflowList")) callbacks.workflowAuthoring = {
424
+ list: () => callback("workflowList", []),
425
+ save: (input) => callback("workflowSave", [input]),
426
+ ...frame.callbacks.includes("workflowEditorRead") && typeof frame.workflowEditorId === "string" ? {
427
+ editor: { id: frame.workflowEditorId, read: () => callback("workflowEditorRead", []) }
428
+ } : {}
429
+ };
430
+ if (frame.callbacks.includes("workflowInvoke")) callbacks.workflowLifecycle = {
431
+ invoke: (name, args) => callback("workflowInvoke", [name, args])
432
+ };
376
433
  if (frame.callbacks.includes("historyList")) callbacks.conversationHistory = { list: (...args) => callback("historyList", args), read: (...args) => callback("historyRead", args) };
377
434
  pluginManager = await createSessionPluginHostManager({ projectPath: "/workspace", reservedToolNames: Object.values(TOOL_DEFINITIONS).map((tool) => tool.name), coreVersion: VERSION });
378
435
  const unavailable = pluginManager.current().status().filter((entry) => entry.state !== "ready");
@@ -399,15 +456,22 @@ async function runGuest(cli) {
399
456
  if (typeof frame.text !== "string") throw new Error("Invalid operator message");
400
457
  turn = new AbortController();
401
458
  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 }));
459
+ const result = withScopeEnforcement(policy, () => session.send(frame.text, callbacks, { signal: turn.signal, generateTitle: frame.generateTitle === true, images: frame.images }));
403
460
  await state();
404
461
  value = await result;
405
462
  turn = void 0;
406
463
  break;
407
464
  }
408
- case "autonomy":
465
+ case "autonomy": {
466
+ if (typeof frame.value !== "string" || !["standard", "recon", "copilot", "yolo"].includes(frame.value)) throw new Error("Invalid autonomy mode");
409
467
  session.setAutonomyMode(frame.value);
410
468
  break;
469
+ }
470
+ case "webSearch": {
471
+ if (typeof frame.value !== "boolean") throw new Error("Invalid web search setting");
472
+ await session.setWebSearchEnabled(frame.value);
473
+ break;
474
+ }
411
475
  case "configure": {
412
476
  const value2 = frame.value;
413
477
  session.configureEngagement({ target: value2.target, ...value2.scope === void 0 ? {} : { scope: value2.scope ? new ScopePolicy(value2.scope) : null } });
@@ -514,7 +578,7 @@ async function setupWorkbench(options, home = process.env.HOME || homedir()) {
514
578
  const workspaceRoot = options.workspace === void 0 ? previous?.workspaceRoot : realpathSync(resolve(options.workspace));
515
579
  if (workspaceRoot !== void 0 && !lstatSync(workspaceRoot).isDirectory()) throw new Error("Workbench workspace must be a directory.");
516
580
  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.`);
581
+ for (const id2 of providers) if (!PROVIDERS.some((provider) => provider.id === id2)) throw new Error(`Unknown provider grant: ${id2}. Use 0 workbench providers.`);
518
582
  const imageArchive = options.image ? resolve(options.image) : previous?.image;
519
583
  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
584
  const cpus = resource(options.cpus, previous?.cpus ?? 2, 64, "CPUs");
@@ -574,7 +638,7 @@ function registerWorkbenchCommand(program) {
574
638
  workbench.command("console-agent", { hidden: true }).action(async () => {
575
639
  await runWorkbenchConsoleGuest();
576
640
  });
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) => {
641
+ 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
642
  try {
579
643
  const config = await setupWorkbench(options);
580
644
  console.log(`SmolVM workbench selected. Approved image: ${config.imageDigest}`);
@@ -606,7 +670,7 @@ function registerWorkbenchCommand(program) {
606
670
  try {
607
671
  const config = loadWorkbenchConfig();
608
672
  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());
673
+ if (options.provider !== void 0) config.providers = options.provider === "none" ? [] : options.provider.split(",").map((id2) => id2.trim());
610
674
  if (options.github !== void 0) config.github = options.github;
611
675
  if (options.currentWorkspace) delete config.workspaceRoot;
612
676
  if (options.clearSandboxImages) delete config.approvedImages;
@@ -656,10 +720,775 @@ function findDashboardInstance(origin) {
656
720
  });
657
721
  }
658
722
 
723
+ // packages/cli/src/web/managed-host-reconnect.ts
724
+ function startManagedHostReconnect(options) {
725
+ const metadata = options.getMetadata ?? loadManagedWorker;
726
+ const now = options.now ?? Date.now;
727
+ const attempts = /* @__PURE__ */ new Map();
728
+ const controller = new AbortController();
729
+ let busy = false;
730
+ const tick = async () => {
731
+ if (busy || controller.signal.aborted) return;
732
+ busy = true;
733
+ try {
734
+ const registry2 = options.getRegistry();
735
+ const hosts = registry2.list().filter((host) => host.id !== "local");
736
+ for (const id2 of attempts.keys()) if (!hosts.some((host) => host.id === id2)) attempts.delete(id2);
737
+ for (const host of hosts) {
738
+ if (controller.signal.aborted) break;
739
+ try {
740
+ if (metadata(host.id).autoReconnect === false || (attempts.get(host.id)?.next ?? 0) > now()) continue;
741
+ const view = await registry2.handshake(host.id).catch(() => null);
742
+ if (view?.backend.status === "connected") {
743
+ attempts.delete(host.id);
744
+ continue;
745
+ }
746
+ if (controller.signal.aborted || !registry2.list().some((item) => item.id === host.id) || metadata(host.id).autoReconnect === false) continue;
747
+ try {
748
+ await options.repair(host.id, controller.signal);
749
+ if (!controller.signal.aborted) await options.onRecovered?.(host.id);
750
+ attempts.delete(host.id);
751
+ } catch {
752
+ const count = (attempts.get(host.id)?.count ?? 0) + 1;
753
+ attempts.set(host.id, { count, next: now() + [6e4, 12e4, 3e5][Math.min(count - 1, 2)] });
754
+ }
755
+ break;
756
+ } catch {
757
+ }
758
+ }
759
+ } catch {
760
+ } finally {
761
+ busy = false;
762
+ }
763
+ };
764
+ const timer = setInterval(() => {
765
+ void tick();
766
+ }, 3e4);
767
+ timer.unref?.();
768
+ void tick();
769
+ return () => {
770
+ clearInterval(timer);
771
+ controller.abort();
772
+ };
773
+ }
774
+
775
+ // packages/cli/src/web/local-provider-relay.ts
776
+ async function createLocalRelayProvider(selection = {}, signal) {
777
+ const [{ createWebConsoleRuntime }, { createWorkbenchProviderBroker }] = await Promise.all([
778
+ import("./operator-services-JOCREEBA.js"),
779
+ import("./dist-ONIRKDH6.js")
780
+ ]);
781
+ signal?.throwIfAborted();
782
+ if (selection.providerId && selection.providerId !== "chatgpt-codex") throw new Error("Local model relay currently supports ChatGPT Codex only. Choose the local ChatGPT connection.");
783
+ const { runtime, info } = await createWebConsoleRuntime({ ...selection, providerId: "chatgpt-codex" });
784
+ signal?.throwIfAborted();
785
+ const provider = runtime.resolvedProvider();
786
+ const model = runtime.resolvedModel();
787
+ 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.");
788
+ const routing = runtime.modelSelection();
789
+ const models = [.../* @__PURE__ */ new Set([model, ...Object.values(routing.agentModels).filter((value) => value !== "auto")])];
790
+ const broker = createWorkbenchProviderBroker({ provider, models, resolveCredentials: runtime.workbenchCredentialResolver(), signal });
791
+ return { info, grant: broker.grant, transport: { provider, models: broker.grant.models, request: (request, requestSignal) => broker.request(request, requestSignal), close: broker.close } };
792
+ }
793
+ function object(value) {
794
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
795
+ }
796
+ function id(value) {
797
+ return typeof value === "string" && value.length > 0 && value.length <= 160 && !/[\x00-\x20\x7f]/.test(value);
798
+ }
799
+ var RelayResponseError = class extends Error {
800
+ constructor(code, status) {
801
+ super("Provider relay transport failed.");
802
+ this.code = code;
803
+ this.status = status;
804
+ this.name = "RelayResponseError";
805
+ }
806
+ code;
807
+ status;
808
+ };
809
+ function safeRelayFailure(error2) {
810
+ if (error2 instanceof RelayResponseError) return { name: error2.name, code: error2.code };
811
+ const name = error2 instanceof Error && ["Error", "TypeError", "SyntaxError", "AbortError", "TimeoutError", "BackendConnectionError"].includes(error2.name) ? error2.name : "OtherError";
812
+ const known = {
813
+ "Provider request cancelled or timed out": "provider_timeout_or_cancel",
814
+ "Provider response stream failed": "provider_stream_failed",
815
+ "Host provider request failed": "provider_request_failed",
816
+ "Provider broker concurrency limit reached": "provider_concurrency",
817
+ "Provider broker session budget exhausted": "provider_budget",
818
+ "Provider broker is closed": "provider_closed",
819
+ "Provider or model is not granted to this workbench": "provider_grant_rejected",
820
+ "Provider request exceeds its byte limit": "provider_input_limit",
821
+ "Invalid provider request JSON": "provider_input_json",
822
+ "Provider broker permits only stateless streamed Responses requests": "provider_body_shape",
823
+ "Provider broker refuses hosted tools and external tool destinations": "provider_tool_destination",
824
+ "Unsupported provider input item": "provider_input_type",
825
+ "Provider broker permits text input only": "provider_text_only",
826
+ "Remote provider relay scope changed.": "remote_scope_changed",
827
+ "Invalid provider cancellation.": "remote_cancel_invalid",
828
+ "Remote provider request exceeds its approval.": "remote_request_not_approved",
829
+ "Remote provider relay exceeded its request budget.": "relay_budget",
830
+ "Provider output exceeds approval.": "provider_output_limit",
831
+ "Provider SSE event exceeds response limit.": "provider_sse_limit",
832
+ "Backend response identity does not match the registered engine.": "remote_identity_changed",
833
+ "Backend response exceeds the transport limit.": "remote_response_limit",
834
+ "Backend request timed out.": "remote_timeout",
835
+ "Backend transport failed.": "remote_transport_failed",
836
+ "Backend request capacity exceeded.": "remote_capacity"
837
+ };
838
+ return { name, code: error2 instanceof Error ? Object.hasOwn(known, error2.message) ? known[error2.message] : "unclassified" : "unclassified" };
839
+ }
840
+ async function json(response) {
841
+ if (!response.ok) {
842
+ await response.body?.cancel();
843
+ throw new RelayResponseError("remote_http_rejected", response.status);
844
+ }
845
+ try {
846
+ return object(await response.json());
847
+ } catch {
848
+ throw new RelayResponseError("remote_invalid_json", response.status);
849
+ }
850
+ }
851
+ async function pause(signal, delayMs = 500) {
852
+ if (signal.aborted) return;
853
+ await new Promise((resolve5) => {
854
+ const abort = () => {
855
+ clearTimeout(timer);
856
+ resolve5();
857
+ };
858
+ const timer = setTimeout(() => {
859
+ signal.removeEventListener("abort", abort);
860
+ resolve5();
861
+ }, delayMs);
862
+ signal.addEventListener("abort", abort, { once: true });
863
+ });
864
+ }
865
+ function retryablePollFailure(error2) {
866
+ if (error2 instanceof RelayResponseError) return [408, 425, 429, 500, 502, 503, 504].includes(error2.status);
867
+ if (!(error2 instanceof Error) || error2.name !== "BackendConnectionError") return false;
868
+ const status = error2.statusCode;
869
+ return status === 504 && error2.message === "Backend request timed out." || status === 502 && error2.message === "Backend transport failed." || status === 429 && error2.message === "Backend request capacity exceeded.";
870
+ }
871
+ function failureTransportStatus(error2, fallback) {
872
+ if (error2 instanceof RelayResponseError) return error2.status;
873
+ if (error2 instanceof Error && error2.name === "BackendConnectionError" && typeof error2.statusCode === "number") return error2.statusCode;
874
+ return fallback;
875
+ }
876
+ function providerSseCompletion(maximumBytes = 8 * 1024 * 1024) {
877
+ const decoder = new TextDecoder();
878
+ let buffer = "", completed = false;
879
+ return (chunk) => {
880
+ if (completed) return true;
881
+ buffer += decoder.decode(chunk, { stream: true });
882
+ let boundary;
883
+ while (boundary = /\r?\n\r?\n/.exec(buffer)) {
884
+ const block = buffer.slice(0, boundary.index);
885
+ if (Buffer.byteLength(block) > maximumBytes) throw new Error("Provider SSE event exceeds response limit.");
886
+ buffer = buffer.slice(boundary.index + boundary[0].length);
887
+ const data = block.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
888
+ if (!data) continue;
889
+ if (data === "[DONE]") {
890
+ completed = true;
891
+ break;
892
+ }
893
+ try {
894
+ const event = JSON.parse(data);
895
+ if (event && ["response.completed", "response.failed", "response.incomplete", "error"].includes(String(event.type))) {
896
+ completed = true;
897
+ break;
898
+ }
899
+ } catch {
900
+ }
901
+ }
902
+ if (Buffer.byteLength(buffer) > maximumBytes) throw new Error("Provider SSE event exceeds response limit.");
903
+ return completed;
904
+ };
905
+ }
906
+ async function createLocalProviderRelay(options) {
907
+ if (options.backendId === "local" || !id(options.backendId)) throw new Error("Provider relay requires a registered remote host.");
908
+ const lifetime = new AbortController();
909
+ const signal = options.signal ? AbortSignal.any([lifetime.signal, options.signal]) : lifetime.signal;
910
+ signal.throwIfAborted();
911
+ const peer = await options.registry.handshake(options.backendId);
912
+ const epoch = peer.handshake?.serverInstanceId;
913
+ if (peer.backend.status !== "connected" || !id(epoch)) throw new Error("Provider relay requires a connected, identified host.");
914
+ const local = await (options.createProvider ?? createLocalRelayProvider)(options.selection ?? {}, signal);
915
+ const base = "/api/console/provider-relays";
916
+ let grant;
917
+ try {
918
+ const response = await json(await options.registry.request(options.backendId, base, { method: "POST", body: { epoch, provider: local.grant.provider, models: local.grant.models, model: local.info.model }, signal }));
919
+ const candidate = object(response.grant);
920
+ if (!id(candidate.id) || candidate.epoch !== epoch || 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.");
921
+ grant = candidate;
922
+ } catch (error2) {
923
+ await local.transport.close?.();
924
+ throw error2;
925
+ }
926
+ const diagnostic = { phase: "registered", lastPollAt: null, seenRequests: 0, activeRequests: 0, bytesForwarded: 0 };
927
+ const grantPath = `${base}/${encodeURIComponent(grant.id)}`;
928
+ let revocation;
929
+ const revokeGrant = () => revocation ??= options.registry.request(options.backendId, grantPath, { method: "DELETE", signal: AbortSignal.timeout(5e3) }).then(async (response) => {
930
+ await response.body?.cancel();
931
+ }).catch(() => {
932
+ });
933
+ const active = /* @__PURE__ */ new Map();
934
+ const seen = /* @__PURE__ */ new Set();
935
+ const tasks = /* @__PURE__ */ new Set();
936
+ let boundSession, polling, closing;
937
+ const abortActive = () => {
938
+ for (const controller of active.values()) controller.abort();
939
+ };
940
+ signal.addEventListener("abort", abortActive, { once: true });
941
+ const remoteRequest = async (path, optionsValue) => {
942
+ const response = await options.registry.request(options.backendId, path, optionsValue);
943
+ diagnostic.lastTransportStatus = response.status;
944
+ return response;
945
+ };
946
+ const send = (request, events, callerSignal) => remoteRequest(`${grantPath}/requests/${encodeURIComponent(request.requestId)}/events`, { method: "POST", body: { epoch, sessionId: boundSession, events }, signal: callerSignal }).then(json);
947
+ async function service(request) {
948
+ const controller = new AbortController();
949
+ active.set(request.requestId, controller);
950
+ diagnostic.phase = "requesting";
951
+ diagnostic.activeRequests = active.size;
952
+ delete diagnostic.error;
953
+ const requestSignal = AbortSignal.any([signal, controller.signal, AbortSignal.timeout(local.grant.limits.timeoutMs)]);
954
+ let reader;
955
+ const cancelReader = () => {
956
+ void reader?.cancel().catch(() => {
957
+ });
958
+ };
959
+ requestSignal.addEventListener("abort", cancelReader, { once: true });
960
+ let stage = "provider_request";
961
+ try {
962
+ const response = await local.transport.request({ provider: request.provider, model: request.model, body: request.body }, requestSignal);
963
+ diagnostic.lastUpstreamStatus = response.status;
964
+ stage = "headers_post";
965
+ await send(request, [{ kind: "headers", status: response.status, contentType: response.headers.get("content-type") || "text/event-stream" }], requestSignal);
966
+ diagnostic.phase = "streaming";
967
+ if (response.body) {
968
+ reader = response.body.getReader();
969
+ let bytes = 0;
970
+ const completed = response.headers.get("content-type")?.toLowerCase().includes("text/event-stream") ? providerSseCompletion(local.grant.limits.maxResponseBytes) : void 0;
971
+ while (!requestSignal.aborted) {
972
+ stage = "reader_read";
973
+ const chunk = await reader.read();
974
+ if (chunk.done) break;
975
+ bytes += chunk.value.byteLength;
976
+ if (bytes > local.grant.limits.maxResponseBytes) throw new Error("Provider output exceeds approval.");
977
+ stage = "chunk_post";
978
+ for (let offset = 0; offset < chunk.value.byteLength; offset += 32 * 1024) {
979
+ const portion = chunk.value.subarray(offset, offset + 32 * 1024);
980
+ await send(request, [{ kind: "chunk", data: Buffer.from(portion).toString("base64") }], requestSignal);
981
+ diagnostic.bytesForwarded += portion.byteLength;
982
+ }
983
+ stage = "sse_parse";
984
+ if (completed?.(chunk.value)) break;
985
+ }
986
+ requestSignal.throwIfAborted();
987
+ }
988
+ stage = "end_post";
989
+ await send(request, [{ kind: "end" }], requestSignal);
990
+ } catch (error2) {
991
+ if (!signal.aborted && !controller.signal.aborted) {
992
+ const failure = safeRelayFailure(error2);
993
+ diagnostic.lastFailureStage = stage;
994
+ diagnostic.lastFailureName = failure.name;
995
+ diagnostic.lastFailureCode = failure.code;
996
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
997
+ diagnostic.error = diagnostic.phase === "requesting" ? "upstream_failed" : "response_rejected";
998
+ await send(request, [{ kind: "error" }], AbortSignal.any([signal, AbortSignal.timeout(5e3)])).catch(() => {
999
+ });
1000
+ }
1001
+ } finally {
1002
+ requestSignal.removeEventListener("abort", cancelReader);
1003
+ controller.abort();
1004
+ if (reader) {
1005
+ await reader.cancel().catch(() => {
1006
+ });
1007
+ reader.releaseLock();
1008
+ }
1009
+ active.delete(request.requestId);
1010
+ diagnostic.activeRequests = active.size;
1011
+ if (!signal.aborted && !active.size) diagnostic.phase = "polling";
1012
+ }
1013
+ }
1014
+ async function poll() {
1015
+ let failureCategory = "poll_rejected";
1016
+ try {
1017
+ if (!active.size) diagnostic.phase = "polling";
1018
+ while (!signal.aborted) {
1019
+ failureCategory = "poll_rejected";
1020
+ let body;
1021
+ try {
1022
+ body = await json(await remoteRequest(`${grantPath}/requests`, { method: "GET", signal }));
1023
+ } catch (error2) {
1024
+ if (signal.aborted || !retryablePollFailure(error2)) throw error2;
1025
+ const failure = safeRelayFailure(error2);
1026
+ diagnostic.phase = "reconnecting";
1027
+ diagnostic.error = "poll_rejected";
1028
+ diagnostic.lastFailureStage = "poll";
1029
+ diagnostic.lastFailureName = failure.name;
1030
+ diagnostic.lastFailureCode = failure.code;
1031
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
1032
+ diagnostic.consecutivePollFailures = (diagnostic.consecutivePollFailures ?? 0) + 1;
1033
+ await pause(signal, Math.min(5e3, 500 * 2 ** Math.min(diagnostic.consecutivePollFailures - 1, 4)));
1034
+ continue;
1035
+ }
1036
+ diagnostic.consecutivePollFailures = 0;
1037
+ if (diagnostic.error === "poll_rejected") delete diagnostic.error;
1038
+ diagnostic.phase = active.size ? "streaming" : "polling";
1039
+ diagnostic.lastPollAt = (/* @__PURE__ */ new Date()).toISOString();
1040
+ failureCategory = "scope_changed";
1041
+ if (body.grantId !== grant.id || body.epoch !== epoch || 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.");
1042
+ for (const requestId of body.cancelled) {
1043
+ if (!id(requestId)) throw new Error("Invalid provider cancellation.");
1044
+ active.get(requestId)?.abort();
1045
+ }
1046
+ for (const value of body.requests) {
1047
+ const request = object(value);
1048
+ failureCategory = "request_rejected";
1049
+ if (!id(request.requestId) || request.grantId !== grant.id || request.epoch !== epoch || 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.");
1050
+ if (seen.has(request.requestId)) continue;
1051
+ 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.");
1052
+ seen.add(request.requestId);
1053
+ diagnostic.seenRequests = seen.size;
1054
+ const task = service(request);
1055
+ tasks.add(task);
1056
+ void task.finally(() => tasks.delete(task));
1057
+ }
1058
+ await pause(signal);
1059
+ }
1060
+ } catch (error2) {
1061
+ if (!signal.aborted) {
1062
+ diagnostic.phase = "failed";
1063
+ diagnostic.error = failureCategory;
1064
+ const failure = safeRelayFailure(error2);
1065
+ diagnostic.lastFailureStage = "poll";
1066
+ diagnostic.lastFailureName = failure.name;
1067
+ diagnostic.lastFailureCode = failure.code;
1068
+ diagnostic.lastFailureTransportStatus = failureTransportStatus(error2, diagnostic.lastTransportStatus);
1069
+ }
1070
+ if (!signal.aborted) options.onError?.("The local model connection to this host ended. Reconnect the host before continuing.");
1071
+ lifetime.abort();
1072
+ } finally {
1073
+ if (signal.aborted && diagnostic.phase !== "failed") diagnostic.phase = "closed";
1074
+ abortActive();
1075
+ await local.transport.close?.();
1076
+ await revokeGrant();
1077
+ }
1078
+ }
1079
+ return {
1080
+ grant,
1081
+ info: local.info,
1082
+ status: () => ({ ...diagnostic }),
1083
+ start(sessionId) {
1084
+ if (!id(sessionId) || signal.aborted || boundSession) throw new Error("Provider relay must bind once to an active remote session.");
1085
+ boundSession = sessionId;
1086
+ polling = poll();
1087
+ },
1088
+ close() {
1089
+ return closing ??= (async () => {
1090
+ diagnostic.phase = "closed";
1091
+ lifetime.abort();
1092
+ abortActive();
1093
+ signal.removeEventListener("abort", abortActive);
1094
+ await local.transport.close?.();
1095
+ await Promise.allSettled([...tasks, ...polling ? [polling] : []]);
1096
+ await revokeGrant();
1097
+ })();
1098
+ }
1099
+ };
1100
+ }
1101
+
1102
+ // packages/cli/src/web/local-provider-sessions.ts
1103
+ var LocalProviderSessionError = class extends Error {
1104
+ constructor(message, statusCode = 400) {
1105
+ super(message);
1106
+ this.statusCode = statusCode;
1107
+ }
1108
+ statusCode;
1109
+ };
1110
+ var resourceId = external_exports.string().min(1).max(160).regex(/^[^\x00-\x20\x7f]+$/);
1111
+ 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();
1112
+ 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();
1113
+ var LocalProviderSessions = class {
1114
+ constructor(options) {
1115
+ this.options = options;
1116
+ }
1117
+ options;
1118
+ sessions = /* @__PURE__ */ new Map();
1119
+ lifetime = new AbortController();
1120
+ creating = 0;
1121
+ reconnecting = /* @__PURE__ */ new Set();
1122
+ key(backendId, sessionId) {
1123
+ return JSON.stringify([backendId, sessionId]);
1124
+ }
1125
+ signal() {
1126
+ return this.options.signal ? AbortSignal.any([this.lifetime.signal, this.options.signal]) : this.lifetime.signal;
1127
+ }
1128
+ assertBackend(backendId) {
1129
+ if (!resourceId.safeParse(backendId).success || backendId === "local") throw new LocalProviderSessionError("Choose a registered remote host.");
1130
+ this.signal().throwIfAborted();
1131
+ }
1132
+ async request(backendId, path, method, body) {
1133
+ const response = await this.options.registry.request(backendId, path, { method, body, signal: this.signal() });
1134
+ if (!response.ok) {
1135
+ await response.body?.cancel();
1136
+ throw new LocalProviderSessionError("The remote host rejected this conversation change.", response.status);
1137
+ }
1138
+ const value = await response.json();
1139
+ if (!value?.session || !resourceId.safeParse(value.session.id).success) throw new LocalProviderSessionError("The remote host returned an invalid conversation.", 502);
1140
+ return value.session;
1141
+ }
1142
+ async create(backendId, input, ownerId = "local") {
1143
+ this.assertBackend(backendId);
1144
+ const parsed = createSchema.safeParse(input);
1145
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid remote conversation configuration; model grants are issued locally.");
1146
+ if (this.sessions.size + this.creating >= 32) throw new LocalProviderSessionError("Close a local model connection before creating another remote conversation.", 429);
1147
+ this.creating++;
1148
+ let relay;
1149
+ try {
1150
+ relay = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection: parsed.data.runtime, signal: this.signal() });
1151
+ 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 } });
1152
+ this.signal().throwIfAborted();
1153
+ const key = this.key(backendId, session.id);
1154
+ if (this.sessions.has(key)) throw new LocalProviderSessionError("The host reused an existing conversation ID.", 409);
1155
+ relay.start(session.id);
1156
+ this.sessions.set(key, { owner: ownerId, relay, selection: parsed.data.runtime ?? {}, configuring: false });
1157
+ return { session };
1158
+ } catch (error2) {
1159
+ await relay?.close();
1160
+ throw error2;
1161
+ } finally {
1162
+ this.creating--;
1163
+ }
1164
+ }
1165
+ async configure(backendId, sessionId, runtime, ownerId = "local") {
1166
+ this.assertBackend(backendId);
1167
+ if (!resourceId.safeParse(sessionId).success) throw new LocalProviderSessionError("Invalid conversation ID.");
1168
+ const managed = this.sessions.get(this.key(backendId, sessionId));
1169
+ if (!managed) return this.reconnect(backendId, sessionId, runtime, ownerId);
1170
+ if (managed.owner !== ownerId) throw new LocalProviderSessionError("This local model connection does not belong to the current workspace.", 404);
1171
+ const parsed = runtimeSchema.safeParse(runtime);
1172
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid local model selection.");
1173
+ if (managed.configuring) throw new LocalProviderSessionError("Wait for the current model change.", 409);
1174
+ managed.configuring = true;
1175
+ let replacement;
1176
+ try {
1177
+ const selection = { ...managed.selection, ...parsed.data };
1178
+ replacement = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection, signal: this.signal() });
1179
+ 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 } });
1180
+ if (session.id !== sessionId) throw new LocalProviderSessionError("The host returned a different conversation.", 409);
1181
+ this.signal().throwIfAborted();
1182
+ replacement.start(sessionId);
1183
+ const previous = managed.relay;
1184
+ managed.relay = replacement;
1185
+ managed.selection = selection;
1186
+ replacement = void 0;
1187
+ await previous.close();
1188
+ return { session };
1189
+ } finally {
1190
+ managed.configuring = false;
1191
+ await replacement?.close();
1192
+ }
1193
+ }
1194
+ /** Explicitly restore a saved local-model marker after either engine restarted. */
1195
+ async reconnect(backendId, sessionId, runtime, ownerId = "local") {
1196
+ this.assertBackend(backendId);
1197
+ if (!resourceId.safeParse(sessionId).success) throw new LocalProviderSessionError("Invalid conversation ID.");
1198
+ const key = this.key(backendId, sessionId), existing = this.sessions.get(key);
1199
+ if (existing) return this.configure(backendId, sessionId, runtime, ownerId);
1200
+ const parsed = runtimeSchema.safeParse(runtime);
1201
+ if (!parsed.success) throw new LocalProviderSessionError("Invalid local model selection.");
1202
+ if (this.reconnecting.has(key)) throw new LocalProviderSessionError("Wait for this conversation to reconnect.", 409);
1203
+ if (this.sessions.size + this.creating >= 32) throw new LocalProviderSessionError("Close a local model connection before reconnecting another conversation.", 429);
1204
+ this.reconnecting.add(key);
1205
+ this.creating++;
1206
+ let replacement;
1207
+ try {
1208
+ const response = await this.options.registry.request(backendId, `/api/console/sessions/${encodeURIComponent(sessionId)}`, { method: "GET", signal: this.signal() });
1209
+ if (!response.ok) {
1210
+ await response.body?.cancel();
1211
+ throw new LocalProviderSessionError("This remote conversation could not be reopened.", response.status);
1212
+ }
1213
+ const snapshot = (await response.json()).snapshot;
1214
+ if (snapshot?.session?.id !== sessionId || snapshot.runtime?.modelSource !== "local") throw new LocalProviderSessionError("Only an existing local-model conversation can reconnect through this route.", 409);
1215
+ if (snapshot.session.status !== "ready") throw new LocalProviderSessionError("Wait until this remote conversation is idle before reconnecting its model.", 409);
1216
+ const selection = runtimeSchema.parse({ providerId: "chatgpt-codex", ...snapshot.runtime.model ? { model: snapshot.runtime.model } : {}, ...parsed.data });
1217
+ replacement = await (this.options.createRelay ?? createLocalProviderRelay)({ registry: this.options.registry, backendId, selection, signal: this.signal() });
1218
+ 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 } });
1219
+ if (session.id !== sessionId) throw new LocalProviderSessionError("The host returned a different conversation.", 409);
1220
+ this.signal().throwIfAborted();
1221
+ replacement.start(sessionId);
1222
+ this.sessions.set(key, { owner: ownerId, relay: replacement, selection, configuring: false });
1223
+ replacement = void 0;
1224
+ return { session };
1225
+ } finally {
1226
+ this.creating--;
1227
+ this.reconnecting.delete(key);
1228
+ await replacement?.close();
1229
+ }
1230
+ }
1231
+ async status(backendId, sessionId, ownerId = "local") {
1232
+ this.assertBackend(backendId);
1233
+ const managed = this.sessions.get(this.key(backendId, sessionId));
1234
+ if (!managed || managed.owner !== ownerId) throw new LocalProviderSessionError("This local model connection does not belong to the current workspace.", 404);
1235
+ const local = managed.relay.status();
1236
+ try {
1237
+ 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)]) });
1238
+ if (!response.ok) {
1239
+ await response.body?.cancel();
1240
+ return { local, remote: { available: false, status: response.status } };
1241
+ }
1242
+ const body = await response.json();
1243
+ if (body.grantId !== managed.relay.grant.id || body.epoch !== managed.relay.grant.epoch || body.sessionId !== sessionId) throw new Error("scope");
1244
+ 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);
1245
+ const cancelledCount = external_exports.number().int().nonnegative().parse(body.cancelledCount);
1246
+ return { local, remote: { available: true, pending, cancelledCount } };
1247
+ } catch {
1248
+ return { local, remote: { available: false } };
1249
+ }
1250
+ }
1251
+ async release(backendId, sessionId, ownerId = "local") {
1252
+ const key = this.key(backendId, sessionId), managed = this.sessions.get(key);
1253
+ if (!managed || managed.owner !== ownerId) return;
1254
+ this.sessions.delete(key);
1255
+ await managed.relay.close();
1256
+ }
1257
+ async dispose() {
1258
+ this.lifetime.abort();
1259
+ const entries = [...this.sessions.values()];
1260
+ this.sessions.clear();
1261
+ await Promise.allSettled(entries.map((entry) => entry.relay.close()));
1262
+ }
1263
+ };
1264
+
1265
+ // packages/cli/src/web/remote-provider-relay.ts
1266
+ import { randomUUID as randomUUID2 } from "node:crypto";
1267
+ var ProviderRelayError = class extends Error {
1268
+ constructor(message, statusCode = 400) {
1269
+ super(message);
1270
+ this.statusCode = statusCode;
1271
+ }
1272
+ statusCode;
1273
+ };
1274
+ var GRANT_IDLE_MS = 30 * 6e4;
1275
+ var REDELIVER_UNACKNOWLEDGED_MS = 5e3;
1276
+ var modelId = external_exports.string().regex(/^[A-Za-z0-9._-]{1,128}$/);
1277
+ 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();
1278
+ 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", [
1279
+ 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(),
1280
+ external_exports.object({ kind: external_exports.literal("chunk"), data: external_exports.string().max(65536).regex(/^[A-Za-z0-9+/]*={0,2}$/) }).strict(),
1281
+ external_exports.object({ kind: external_exports.literal("end") }).strict(),
1282
+ external_exports.object({ kind: external_exports.literal("error") }).strict()
1283
+ ])).min(1).max(16) }).strict();
1284
+ var RemoteProviderRelayBroker = class {
1285
+ epoch;
1286
+ #grants = /* @__PURE__ */ new Map();
1287
+ #disposed = false;
1288
+ constructor(options) {
1289
+ this.epoch = options.epoch;
1290
+ }
1291
+ create(input) {
1292
+ const value = grantInput.parse(input);
1293
+ if (this.#disposed || value.epoch !== this.epoch) throw new ProviderRelayError("Provider relay engine epoch changed.", 409);
1294
+ if (!value.models.includes(value.model) || new Set(value.models).size !== value.models.length) throw new ProviderRelayError("Provider relay requires an exact model allowlist.");
1295
+ for (const [id2, state] of this.#grants) if (Date.parse(state.grant.expiresAt) <= Date.now()) this.close(id2);
1296
+ if (this.#grants.size >= 32) throw new ProviderRelayError("Provider relay grant limit reached.", 429);
1297
+ 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() };
1298
+ this.#grants.set(grant.id, { grant, requests: /* @__PURE__ */ new Map(), cancelled: [] });
1299
+ return structuredClone(grant);
1300
+ }
1301
+ #get(id2) {
1302
+ const state = this.#grants.get(id2);
1303
+ if (!state || this.#disposed) throw new ProviderRelayError("Provider relay grant is unavailable. Reconnect the local model source.", 410);
1304
+ if (Date.parse(state.grant.expiresAt) <= Date.now()) {
1305
+ this.close(id2);
1306
+ throw new ProviderRelayError("Provider relay grant expired. Reconnect the local model source.", 410);
1307
+ }
1308
+ return state;
1309
+ }
1310
+ bind(id2, sessionId) {
1311
+ const state = this.#get(id2);
1312
+ if (state.sessionId && state.sessionId !== sessionId) throw new ProviderRelayError("Provider relay grant belongs to another conversation.", 409);
1313
+ state.sessionId = sessionId;
1314
+ return structuredClone(state.grant);
1315
+ }
1316
+ transport(id2, sessionId) {
1317
+ const grant = this.bind(id2, sessionId);
1318
+ return { provider: grant.provider, models: grant.models, identity: `relay:${grant.epoch}:${grant.id}`, request: (envelope, signal) => this.request(id2, sessionId, envelope, signal) };
1319
+ }
1320
+ runtime(id2, sessionId, selection = {}) {
1321
+ const grant = this.bind(id2, sessionId);
1322
+ if (selection.providerId && selection.providerId !== grant.provider) throw new ProviderRelayError("Provider is outside the local model grant.", 403);
1323
+ return new LlmApiRuntime({
1324
+ type: "api",
1325
+ timeout: 12e4,
1326
+ model: selection.model ?? grant.model,
1327
+ provider: "chatgpt-codex",
1328
+ singleModel: selection.singleModel,
1329
+ agentModels: selection.agentModels,
1330
+ autoRoute: selection.autoRoute,
1331
+ providerTransport: this.transport(id2, sessionId)
1332
+ });
1333
+ }
1334
+ request(id2, sessionId, envelope, signal) {
1335
+ const state = this.#get(id2);
1336
+ 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);
1337
+ signal?.throwIfAborted();
1338
+ if (state.requests.size >= 4) throw new ProviderRelayError("Local model request limit reached.", 429);
1339
+ if (Buffer.byteLength(envelope.body) > 4 * 1024 * 1024) throw new ProviderRelayError("Local model request exceeds 4 MiB.", 413);
1340
+ const body = JSON.parse(envelope.body);
1341
+ if (body.model !== envelope.model || body.background === true || body.store === true || !Array.isArray(body.input)) throw new ProviderRelayError("Invalid granted Responses request.");
1342
+ return new Promise((resolve5, reject) => {
1343
+ const requestId = randomUUID2();
1344
+ const cancel = () => this.#finish(state, pending, new Error("Local provider request cancelled."));
1345
+ const pending = {
1346
+ ...envelope,
1347
+ requestId,
1348
+ grantId: id2,
1349
+ epoch: this.epoch,
1350
+ sessionId,
1351
+ startedAt: Date.now(),
1352
+ delivered: false,
1353
+ received: 0,
1354
+ buffered: 0,
1355
+ ended: false,
1356
+ headers: false,
1357
+ queue: [],
1358
+ resolve: resolve5,
1359
+ reject,
1360
+ timer: setTimeout(() => this.#finish(state, pending, new Error("Local provider relay timed out.")), 12e4),
1361
+ cleanup: () => signal?.removeEventListener("abort", cancel)
1362
+ };
1363
+ signal?.addEventListener("abort", cancel, { once: true });
1364
+ state.requests.set(requestId, pending);
1365
+ if (signal?.aborted) cancel();
1366
+ });
1367
+ }
1368
+ status(id2) {
1369
+ const state = this.#get(id2);
1370
+ return {
1371
+ grantId: id2,
1372
+ epoch: this.epoch,
1373
+ sessionId: state.sessionId,
1374
+ expiresAt: state.grant.expiresAt,
1375
+ 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) })),
1376
+ cancelledCount: state.cancelled.length
1377
+ };
1378
+ }
1379
+ poll(id2) {
1380
+ const state = this.#get(id2);
1381
+ state.grant.expiresAt = new Date(Date.now() + GRANT_IDLE_MS).toISOString();
1382
+ const requests = [...state.requests.values()];
1383
+ const next = requests.find((request) => !request.delivered) ?? requests.find((request) => !request.headers && Date.now() - (request.deliveredAt ?? request.startedAt) >= REDELIVER_UNACKNOWLEDGED_MS);
1384
+ if (next) {
1385
+ next.delivered = true;
1386
+ next.deliveredAt = Date.now();
1387
+ }
1388
+ const cancelled = state.cancelled.splice(0);
1389
+ 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 };
1390
+ }
1391
+ #finish(state, request, error2) {
1392
+ if (request.ended) return;
1393
+ request.ended = true;
1394
+ clearTimeout(request.timer);
1395
+ request.cleanup();
1396
+ state.requests.delete(request.requestId);
1397
+ if (error2) {
1398
+ state.cancelled.push(request.requestId);
1399
+ state.cancelled = state.cancelled.slice(-64);
1400
+ if (!request.headers) request.reject(error2);
1401
+ request.controller?.error(error2);
1402
+ } else if (!request.headers) request.reject(new Error("Provider relay ended without response headers."));
1403
+ else if (request.queue.length === 0) request.controller?.close();
1404
+ }
1405
+ events(id2, requestId, input) {
1406
+ if (Buffer.byteLength(JSON.stringify(input)) > 512 * 1024) throw new ProviderRelayError("Provider event batch exceeds 512 KiB.", 413);
1407
+ const state = this.#get(id2);
1408
+ const value = eventsInput.parse(input);
1409
+ if (value.epoch !== this.epoch || value.sessionId !== state.sessionId) throw new ProviderRelayError("Provider relay ownership or epoch changed.", 409);
1410
+ const pending = state.requests.get(requestId);
1411
+ if (!pending) throw new ProviderRelayError("Provider request is no longer pending.", 410);
1412
+ for (const event of value.events) {
1413
+ if (pending.ended) throw new ProviderRelayError("Provider response has ended.", 409);
1414
+ if (event.kind === "headers") {
1415
+ if (pending.headers) throw new ProviderRelayError("Duplicate provider response headers.", 409);
1416
+ pending.headers = true;
1417
+ const stream = new ReadableStream({ start: (controller) => {
1418
+ pending.controller = controller;
1419
+ }, pull: (controller) => {
1420
+ const chunk = pending.queue.shift();
1421
+ if (chunk) {
1422
+ pending.buffered -= chunk.byteLength;
1423
+ controller.enqueue(chunk);
1424
+ } else if (pending.ended) controller.close();
1425
+ }, cancel: () => this.#finish(state, pending, new Error("Provider response reader cancelled.")) });
1426
+ pending.resolve(new Response(stream, { status: event.status, headers: { "content-type": event.contentType } }));
1427
+ } else if (event.kind === "chunk") {
1428
+ if (!pending.headers) throw new ProviderRelayError("Provider headers must precede body chunks.", 409);
1429
+ const bytes = Buffer.from(event.data, "base64");
1430
+ if (bytes.toString("base64") !== event.data || bytes.byteLength > 49152) throw new ProviderRelayError("Invalid provider body chunk.");
1431
+ pending.received += bytes.byteLength;
1432
+ if (pending.received > 16 * 1024 * 1024 || pending.buffered + bytes.byteLength > 2 * 1024 * 1024) {
1433
+ this.#finish(state, pending, new Error("Provider response exceeded relay limits."));
1434
+ throw new ProviderRelayError("Provider response exceeded relay limits.", 413);
1435
+ }
1436
+ if ((pending.controller?.desiredSize ?? 0) > 0 && pending.queue.length === 0) pending.controller.enqueue(bytes);
1437
+ else {
1438
+ pending.queue.push(bytes);
1439
+ pending.buffered += bytes.byteLength;
1440
+ }
1441
+ } else this.#finish(state, pending, event.kind === "error" ? new Error("Local model source disconnected or failed.") : void 0);
1442
+ }
1443
+ return { accepted: true };
1444
+ }
1445
+ close(id2) {
1446
+ const state = this.#grants.get(id2);
1447
+ if (!state) return;
1448
+ for (const pending of [...state.requests.values()]) this.#finish(state, pending, new Error("Local model source disconnected."));
1449
+ this.#grants.delete(id2);
1450
+ }
1451
+ dispose() {
1452
+ for (const id2 of [...this.#grants.keys()]) this.close(id2);
1453
+ this.#disposed = true;
1454
+ }
1455
+ };
1456
+ async function handleRemoteProviderRelayRequest(req, res, url, broker, readBody) {
1457
+ const base = "/api/console/provider-relays";
1458
+ if (url.pathname !== base && !url.pathname.startsWith(`${base}/`)) return false;
1459
+ const send = (status, value) => {
1460
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
1461
+ res.end(JSON.stringify(value));
1462
+ };
1463
+ try {
1464
+ if (url.pathname === base && req.method === "POST") {
1465
+ send(201, { grant: broker.create(await readBody()) });
1466
+ return true;
1467
+ }
1468
+ const status = /^\/api\/console\/provider-relays\/([a-f0-9-]{36})\/status$/.exec(url.pathname);
1469
+ if (status) {
1470
+ if (req.method !== "GET") throw new ProviderRelayError("Method not allowed.", 405);
1471
+ send(200, broker.status(status[1]));
1472
+ return true;
1473
+ }
1474
+ const match = /^\/api\/console\/provider-relays\/([a-f0-9-]{36})(?:\/requests(?:\/([a-f0-9-]{36})\/events)?)?$/.exec(url.pathname);
1475
+ if (!match) throw new ProviderRelayError("Unknown provider relay route.", 404);
1476
+ if (req.method === "DELETE" && !url.pathname.endsWith("/requests") && !match[2]) {
1477
+ broker.close(match[1]);
1478
+ send(200, { closed: true });
1479
+ } else if (req.method === "GET" && url.pathname.endsWith("/requests")) send(200, broker.poll(match[1]));
1480
+ else if (req.method === "POST" && match[2]) send(200, broker.events(match[1], match[2], await readBody()));
1481
+ else throw new ProviderRelayError("Method not allowed.", 405);
1482
+ } catch (error2) {
1483
+ send(error2 instanceof ProviderRelayError ? error2.statusCode : 400, { error: error2 instanceof ProviderRelayError ? error2.message : "Invalid provider relay request." });
1484
+ }
1485
+ return true;
1486
+ }
1487
+
659
1488
  // packages/cli/src/web/report-artifacts.ts
660
1489
  var ReportArtifactError = class extends Error {
661
- constructor(message2, statusCode) {
662
- super(message2);
1490
+ constructor(message, statusCode) {
1491
+ super(message);
663
1492
  this.statusCode = statusCode;
664
1493
  }
665
1494
  statusCode;
@@ -711,13 +1540,13 @@ function retainedFindingSnapshot(ids, dbPath) {
711
1540
  try {
712
1541
  const findings = [];
713
1542
  const scanIds = /* @__PURE__ */ new Set();
714
- for (const id of new Set(ids)) {
715
- const row = db.getFinding(id);
1543
+ for (const id2 of new Set(ids)) {
1544
+ const row = db.getFinding(id2);
716
1545
  if (!row) throw new ReportArtifactError("A selected finding was not found in this engine.", 404);
717
1546
  scanIds.add(row.scanId);
718
- findings.push(loadFindingFocus(id, { dbPath }).finding);
1547
+ findings.push(loadFindingFocus(id2, { dbPath }).finding);
719
1548
  }
720
- const scans = [...scanIds].map((id) => db.getScan(id)).filter((scan) => scan !== void 0);
1549
+ const scans = [...scanIds].map((id2) => db.getScan(id2)).filter((scan) => scan !== void 0);
721
1550
  return {
722
1551
  target: scans.length === 1 ? scans[0].target : "Selected findings from multiple runs",
723
1552
  scanDepth: "default",
@@ -763,7 +1592,7 @@ function handleFindingImpactRequest(pathname, method, input, options) {
763
1592
  }
764
1593
 
765
1594
  // 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";
1595
+ import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
767
1596
  import { closeSync, constants, fstatSync, mkdirSync, openSync, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync } from "node:fs";
768
1597
  import { join } from "node:path";
769
1598
 
@@ -771,724 +1600,6 @@ import { join } from "node:path";
771
1600
  import { readFileSync } from "node:fs";
772
1601
  import { createHash, randomBytes, scrypt as scryptCallback, timingSafeEqual } from "node:crypto";
773
1602
  import { promisify } from "node:util";
774
-
775
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/buffer_utils.js
776
- var encoder = new TextEncoder();
777
- var decoder = new TextDecoder();
778
- var strictDecoder = new TextDecoder("utf-8", { fatal: true });
779
- var MAX_INT32 = 2 ** 32;
780
- function concat(...buffers) {
781
- const size = buffers.reduce((acc, { length }) => acc + length, 0), buf = new Uint8Array(size);
782
- let i = 0;
783
- for (const buffer of buffers)
784
- buf.set(buffer, i), i += buffer.length;
785
- return buf;
786
- }
787
- var NON_ASCII = /[^\x00-\x7f]/;
788
- function encode(string) {
789
- if (typeof string == "string" && string.length >= 128) {
790
- if (NON_ASCII.test(string))
791
- throw new TypeError("non-ASCII string encountered in encode()");
792
- return encoder.encode(string);
793
- }
794
- const bytes = new Uint8Array(string.length);
795
- for (let i = 0; i < string.length; i++) {
796
- const code = string.charCodeAt(i);
797
- if (code > 127)
798
- throw new TypeError("non-ASCII string encountered in encode()");
799
- bytes[i] = code;
800
- }
801
- return bytes;
802
- }
803
- function decodeBase64(encoded, url = false) {
804
- if (Uint8Array.fromBase64)
805
- return Uint8Array.fromBase64(encoded, { alphabet: url ? "base64url" : "base64" });
806
- if (url) {
807
- if (encoded.includes("+") || encoded.includes("/"))
808
- throw new TypeError("Invalid base64url");
809
- encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
810
- }
811
- const binary = atob(encoded), bytes = new Uint8Array(binary.length);
812
- for (let i = 0; i < binary.length; i++)
813
- bytes[i] = binary.charCodeAt(i);
814
- return bytes;
815
- }
816
-
817
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/util/errors.js
818
- var JOSEError = class extends Error {
819
- static code = "ERR_JOSE_GENERIC";
820
- code = "ERR_JOSE_GENERIC";
821
- constructor(message2, options) {
822
- super(message2, options), this.name = this.constructor.name, Error.captureStackTrace?.(this, this.constructor);
823
- }
824
- };
825
- var JWTClaimValidationFailed = class extends JOSEError {
826
- static code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
827
- code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
828
- claim;
829
- reason;
830
- payload;
831
- constructor(message2, payload, claim = "unspecified", reason = "unspecified") {
832
- super(message2, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
833
- }
834
- };
835
- var JWTExpired = class extends JOSEError {
836
- static code = "ERR_JWT_EXPIRED";
837
- code = "ERR_JWT_EXPIRED";
838
- claim;
839
- reason;
840
- payload;
841
- constructor(message2, payload, claim = "unspecified", reason = "unspecified") {
842
- super(message2, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
843
- }
844
- };
845
- var JOSEAlgNotAllowed = class extends JOSEError {
846
- static code = "ERR_JOSE_ALG_NOT_ALLOWED";
847
- code = "ERR_JOSE_ALG_NOT_ALLOWED";
848
- };
849
- var JOSENotSupported = class extends JOSEError {
850
- static code = "ERR_JOSE_NOT_SUPPORTED";
851
- code = "ERR_JOSE_NOT_SUPPORTED";
852
- };
853
- var JWSInvalid = class extends JOSEError {
854
- static code = "ERR_JWS_INVALID";
855
- code = "ERR_JWS_INVALID";
856
- };
857
- var JWTInvalid = class extends JOSEError {
858
- static code = "ERR_JWT_INVALID";
859
- code = "ERR_JWT_INVALID";
860
- };
861
- var JWKSInvalid = class extends JOSEError {
862
- static code = "ERR_JWKS_INVALID";
863
- code = "ERR_JWKS_INVALID";
864
- };
865
- var JWKSNoMatchingKey = class extends JOSEError {
866
- static code = "ERR_JWKS_NO_MATCHING_KEY";
867
- code = "ERR_JWKS_NO_MATCHING_KEY";
868
- constructor(message2 = "no applicable key found in the JSON Web Key Set", options) {
869
- super(message2, options);
870
- }
871
- };
872
- var JWKSMultipleMatchingKeys = class extends JOSEError {
873
- [Symbol.asyncIterator] = async function* () {
874
- };
875
- static code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
876
- code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
877
- constructor(message2 = "multiple matching keys found in the JSON Web Key Set", options) {
878
- super(message2, options);
879
- }
880
- };
881
- var JWKSTimeout = class extends JOSEError {
882
- static code = "ERR_JWKS_TIMEOUT";
883
- code = "ERR_JWKS_TIMEOUT";
884
- constructor(message2 = "request timed out", options) {
885
- super(message2, options);
886
- }
887
- };
888
- var JWSSignatureVerificationFailed = class extends JOSEError {
889
- static code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
890
- code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
891
- constructor(message2 = "signature verification failed", options) {
892
- super(message2, options);
893
- }
894
- };
895
-
896
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/util/base64url.js
897
- var invalid = "The input to be decoded is not correctly encoded.";
898
- function decode(input) {
899
- try {
900
- return decodeBase64(typeof input == "string" ? input : decoder.decode(input), true);
901
- } catch (cause) {
902
- throw new TypeError(invalid, { cause });
903
- }
904
- }
905
-
906
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/validate.js
907
- function isObject(input) {
908
- if (typeof input != "object" || input === null || Object.prototype.toString.call(input) !== "[object Object]")
909
- return false;
910
- const prototype = Object.getPrototypeOf(input);
911
- return prototype === null || Object.getPrototypeOf(prototype) === null;
912
- }
913
- function isJwkSet(input) {
914
- return isObject(input) && Array.isArray(input.keys) && Array.from(input.keys).every(isObject);
915
- }
916
- function isDisjoint(...headers) {
917
- const parameters = /* @__PURE__ */ new Set();
918
- for (const header of headers)
919
- if (header)
920
- for (const parameter of Object.keys(header)) {
921
- if (parameters.has(parameter))
922
- return false;
923
- parameters.add(parameter);
924
- }
925
- return true;
926
- }
927
- function decodeBase64url(value, label, ErrorClass) {
928
- try {
929
- return decode(value);
930
- } catch {
931
- throw new ErrorClass(`Failed to base64url decode the ${label}`);
932
- }
933
- }
934
- function encodeBase64url(value, label, ErrorClass) {
935
- try {
936
- return encode(value);
937
- } catch {
938
- throw new ErrorClass(`The ${label} is not a valid base64url string`);
939
- }
940
- }
941
- function parseJoseHeader(b64, ErrorClass, message2) {
942
- let parsed;
943
- try {
944
- parsed = JSON.parse(strictDecoder.decode(decode(b64)));
945
- } catch {
946
- throw new ErrorClass(message2);
947
- }
948
- if (!isObject(parsed))
949
- throw new ErrorClass(message2);
950
- return parsed;
951
- }
952
- var JWS_RECOGNIZED = { __proto__: null, b64: true };
953
- function validateAlgorithms(option, algorithms) {
954
- if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s != "string")))
955
- throw new TypeError(`"${option}" option must be an array of strings`);
956
- return algorithms === void 0 ? void 0 : new Set(algorithms);
957
- }
958
- function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
959
- if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0)
960
- throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected');
961
- if (!protectedHeader || protectedHeader.crit === void 0)
962
- return [];
963
- if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input != "string" || input.length === 0))
964
- throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');
965
- const recognized = recognizedOption === void 0 ? recognizedDefault : { __proto__: null, ...recognizedOption, ...recognizedDefault };
966
- for (const parameter of protectedHeader.crit) {
967
- if (!(parameter in recognized))
968
- throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
969
- if (!Object.hasOwn(joseHeader, parameter) || joseHeader[parameter] === void 0)
970
- throw new Err(`Extension Header Parameter "${parameter}" is missing`);
971
- if (recognized[parameter] && (!Object.hasOwn(protectedHeader, parameter) || protectedHeader[parameter] === void 0))
972
- throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
973
- }
974
- return protectedHeader.crit;
975
- }
976
- function validateB64(protectedHeader, extensions) {
977
- if (extensions.includes("b64")) {
978
- const b64 = protectedHeader.b64;
979
- if (typeof b64 != "boolean")
980
- throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
981
- return b64;
982
- }
983
- return true;
984
- }
985
-
986
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/key.js
987
- var tag = (key) => key[Symbol.toStringTag];
988
- var jwkMatchesOp = (entry, key, usage) => {
989
- const { alg } = entry;
990
- if (key.use !== void 0) {
991
- const expected = usage === "sign" || usage === "verify" ? "sig" : "enc";
992
- if (key.use !== expected)
993
- throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`);
994
- }
995
- if (key.alg !== void 0 && key.alg !== alg)
996
- throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`);
997
- if (Array.isArray(key.key_ops)) {
998
- const expectedKeyOp = usage === "encrypt" || usage === "decrypt" ? entry.ops?.[usage === "encrypt" ? 0 : 1] : usage;
999
- if (expectedKeyOp && !key.key_ops.includes(expectedKeyOp))
1000
- throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`);
1001
- }
1002
- };
1003
- async function prepareKey(entry, key, usage) {
1004
- const { alg, secret } = entry, privateKey = usage === "decrypt" || usage === "sign";
1005
- if (secret && key instanceof Uint8Array)
1006
- return key;
1007
- let normalized, keyObject;
1008
- if (isObject(key)) {
1009
- if (normalized = normalizeJwk(key), typeof normalized.kty != "string")
1010
- throw invalidKeyType(alg, key, secret);
1011
- if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (privateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === void 0 && normalized.priv === void 0)))
1012
- throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${privateKey ? "private" : "public"} JWK`);
1013
- if (jwkMatchesOp(entry, normalized, usage), normalized.kty === "oct")
1014
- return decode(normalized.k);
1015
- if (!Object.isFrozen(key)) {
1016
- const { key_ops } = key;
1017
- Array.isArray(key_ops) && Object.freeze(key_ops), Object.freeze(key);
1018
- }
1019
- } else {
1020
- if (!isKeyLike(key))
1021
- throw invalidKeyType(alg, key, secret);
1022
- const expectedType = secret ? "secret" : privateKey ? "private" : "public";
1023
- if (key.type !== expectedType && (secret || ["secret", "public", "private"].includes(key.type)))
1024
- throw new TypeError(`${tag(key)} instances must be of type "${expectedType}" for the ${alg} algorithm`);
1025
- if (isCryptoKey(key))
1026
- return key;
1027
- if (keyObject = key, keyObject.type === "secret")
1028
- return keyObject.export();
1029
- }
1030
- cache ||= /* @__PURE__ */ new WeakMap();
1031
- const cacheKey = key;
1032
- let cached = cache.get(cacheKey);
1033
- if (cached?.[alg])
1034
- return cached[alg];
1035
- if (cached || cache.set(cacheKey, cached = {}), keyObject && typeof keyObject.toCryptoKey == "function") {
1036
- const isPublic = keyObject.type === "public", crv = nist[keyObject.asymmetricKeyDetails?.namedCurve], params = entry.resolve?.({ crv, asymmetricKeyType: keyObject.asymmetricKeyType }) ?? entry.subtle;
1037
- return cached[alg] = keyObject.toCryptoKey(params, isPublic, entry.usages[isPublic ? 0 : 1]);
1038
- }
1039
- return normalized ??= keyObject.export({ format: "jwk" }), normalized.alg = alg, cached[alg] = await jwkToKey(entry, normalized);
1040
- }
1041
- var cache;
1042
- var nist = {
1043
- __proto__: null,
1044
- prime256v1: "P-256",
1045
- secp384r1: "P-384",
1046
- secp521r1: "P-521"
1047
- };
1048
- var isCryptoKey = (key) => {
1049
- if (key?.[Symbol.toStringTag] === "CryptoKey")
1050
- return true;
1051
- try {
1052
- return key instanceof CryptoKey;
1053
- } catch {
1054
- return false;
1055
- }
1056
- };
1057
- var isKeyObject = (key) => key?.[Symbol.toStringTag] === "KeyObject";
1058
- var isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);
1059
- function message(msg, actual, ...types) {
1060
- if (types.length > 2) {
1061
- const last = types.pop();
1062
- msg += `one of type ${types.join(", ")}, or ${last}.`;
1063
- } else types.length === 2 ? msg += `one of type ${types[0]} or ${types[1]}.` : msg += `of type ${types[0]}.`;
1064
- return actual == null ? msg += ` Received ${actual}` : typeof actual == "function" && actual.name ? msg += ` Received function ${actual.name}` : typeof actual == "object" && actual != null && actual.constructor?.name && (msg += ` Received an instance of ${actual.constructor.name}`), msg;
1065
- }
1066
- function invalidKeyType(alg, actual, secret) {
1067
- const types = ["CryptoKey", "KeyObject", "JSON Web Key"];
1068
- return secret && types.push("Uint8Array"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types));
1069
- }
1070
- var unusable = (name, prop = "algorithm.name") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
1071
- function checkUsage(key, usage) {
1072
- if (usage && !key.usages.includes(usage))
1073
- throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
1074
- }
1075
- function checkModulusLength(alg, key) {
1076
- const { modulusLength } = key.algorithm;
1077
- if (typeof modulusLength != "number" || modulusLength < 2048)
1078
- throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
1079
- }
1080
- function checkCryptoKey(key, expected, usage) {
1081
- const algorithm = key.algorithm;
1082
- if (algorithm.name !== expected.name)
1083
- throw unusable(expected.name);
1084
- if (expected.hash && algorithm.hash?.name !== expected.hash)
1085
- throw unusable(expected.hash, "algorithm.hash");
1086
- if (expected.namedCurve && algorithm.namedCurve !== expected.namedCurve)
1087
- throw unusable(expected.namedCurve, "algorithm.namedCurve");
1088
- if (expected.length !== void 0 && algorithm.length !== expected.length)
1089
- throw unusable(expected.length, "algorithm.length");
1090
- checkUsage(key, usage);
1091
- }
1092
- function snapshotJwk(jwk) {
1093
- return { __proto__: null, ...jwk };
1094
- }
1095
- function normalizeJwk(jwk) {
1096
- const normalized = snapshotJwk(jwk);
1097
- if (normalized.ext !== void 0 && typeof normalized.ext != "boolean")
1098
- throw new TypeError('"ext" (Extractable) Parameter must be a boolean');
1099
- if (normalized.key_ops !== void 0) {
1100
- const value = normalized.key_ops, keyOps = Array.isArray(value) ? [...value] : void 0;
1101
- if (!keyOps || keyOps.some((operation) => typeof operation != "string") || new Set(keyOps).size !== keyOps.length)
1102
- throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings');
1103
- normalized.key_ops = keyOps;
1104
- }
1105
- return normalized;
1106
- }
1107
- async function jwkToKey(entry, jwk, extractable) {
1108
- if (!entry.kty.includes(jwk.kty))
1109
- throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
1110
- const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle, isPrivate = !!(jwk.d || jwk.priv), keyData = { ...jwk, ext: extractable ?? jwk.ext };
1111
- return keyData.kty !== "AKP" && delete keyData.alg, delete keyData.use, crypto.subtle.importKey("jwk", keyData, algorithm, keyData.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);
1112
- }
1113
- async function rawKey(key, expected, usage, extractable = false) {
1114
- return key instanceof Uint8Array && (key = await crypto.subtle.importKey("raw", key, expected, extractable, [usage])), checkCryptoKey(key, expected, usage), key;
1115
- }
1116
-
1117
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/key_descriptor.js
1118
- function table(entries) {
1119
- const out = { __proto__: null };
1120
- for (const alg in entries)
1121
- out[alg] = { ...entries[alg], alg };
1122
- return out;
1123
- }
1124
-
1125
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/jws_algorithms.js
1126
- var sig = [["verify"], ["sign"]];
1127
- function hmac(bits) {
1128
- const subtle = { name: "HMAC", hash: `SHA-${bits}` };
1129
- return { kty: ["oct"], secret: true, subtle, signing: subtle, usages: sig };
1130
- }
1131
- function rsa(bits, saltLength) {
1132
- const subtle = { name: saltLength ? "RSA-PSS" : "RSASSA-PKCS1-v1_5", hash: `SHA-${bits}` };
1133
- return {
1134
- kty: ["RSA"],
1135
- subtle,
1136
- signing: saltLength ? { ...subtle, saltLength } : subtle,
1137
- usages: sig,
1138
- minRsaBits: 2048
1139
- };
1140
- }
1141
- function ecdsa(crv, bits) {
1142
- return {
1143
- kty: ["EC"],
1144
- crv,
1145
- subtle: { name: "ECDSA", namedCurve: crv },
1146
- signing: { name: "ECDSA", hash: `SHA-${bits}` },
1147
- usages: sig
1148
- };
1149
- }
1150
- function eddsa() {
1151
- const subtle = { name: "Ed25519" };
1152
- return {
1153
- kty: ["OKP"],
1154
- crv: "Ed25519",
1155
- subtle,
1156
- signing: subtle,
1157
- usages: sig
1158
- };
1159
- }
1160
- function mldsa(bits) {
1161
- const subtle = { name: `ML-DSA-${bits}` };
1162
- return {
1163
- kty: ["AKP"],
1164
- subtle,
1165
- signing: subtle,
1166
- usages: sig
1167
- };
1168
- }
1169
- var JWS = table({
1170
- HS256: hmac(256),
1171
- HS384: hmac(384),
1172
- HS512: hmac(512),
1173
- RS256: rsa(256),
1174
- RS384: rsa(384),
1175
- RS512: rsa(512),
1176
- PS256: rsa(256, 32),
1177
- PS384: rsa(384, 48),
1178
- PS512: rsa(512, 64),
1179
- ES256: ecdsa("P-256", 256),
1180
- ES384: ecdsa("P-384", 384),
1181
- ES512: ecdsa("P-521", 512),
1182
- EdDSA: eddsa(),
1183
- Ed25519: eddsa(),
1184
- "ML-DSA-44": mldsa(44),
1185
- "ML-DSA-65": mldsa(65),
1186
- "ML-DSA-87": mldsa(87)
1187
- });
1188
- function jwsAlgorithm(alg) {
1189
- const entry = typeof alg == "string" ? JWS[alg] : void 0;
1190
- if (!entry)
1191
- throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
1192
- return entry;
1193
- }
1194
-
1195
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/jws_verify.js
1196
- function prepareVerify(options) {
1197
- return [options && validateAlgorithms("algorithms", options.algorithms), options?.crit];
1198
- }
1199
- function parseProtectedHeader(encodedProtected) {
1200
- return encodedProtected === void 0 ? {} : parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid");
1201
- }
1202
- function encodeCompactUnencodedPayload(payload) {
1203
- try {
1204
- return encode(payload);
1205
- } catch {
1206
- throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters");
1207
- }
1208
- }
1209
- async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {
1210
- const { protected: encodedProtected, header, payload: inputPayload } = jws, parsedProt = parsedProtected ?? parseProtectedHeader(encodedProtected);
1211
- if (!isDisjoint(parsedProt, header))
1212
- throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
1213
- const joseHeader = { ...parsedProt, ...header }, b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader)), { alg } = joseHeader;
1214
- if (typeof alg != "string" || !alg)
1215
- throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
1216
- if (shared[0] && !shared[0].has(alg))
1217
- throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
1218
- if (b64) {
1219
- if (typeof inputPayload != "string")
1220
- throw new JWSInvalid("JWS Payload must be a string");
1221
- } else if (typeof inputPayload != "string" && !(inputPayload instanceof Uint8Array))
1222
- throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
1223
- const signingPayload = b64 || typeof inputPayload != "string" ? inputPayload : encodeUnencodedPayload(inputPayload);
1224
- let resolvedKey = false;
1225
- typeof key == "function" && (key = await key(parsedProt, jws), resolvedKey = true);
1226
- const entry = jwsAlgorithm(alg), data = concat(encodedProtected !== void 0 ? encode(encodedProtected) : new Uint8Array(), encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), k = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(k, entry.subtle, "verify");
1227
- entry.minRsaBits && checkModulusLength(entry.alg, cryptoKey);
1228
- let verified = false;
1229
- try {
1230
- verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, data);
1231
- } catch {
1232
- }
1233
- if (!verified)
1234
- throw new JWSSignatureVerificationFailed();
1235
- const result = { payload: typeof signingPayload == "string" ? decodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload };
1236
- return encodedProtected !== void 0 && (result.protectedHeader = parsedProt), header !== void 0 && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: k }, b64] : [result, b64];
1237
- }
1238
- async function verifyCompact(jws, shared, key) {
1239
- if (jws instanceof Uint8Array && (jws = decoder.decode(jws)), typeof jws != "string")
1240
- throw new JWSInvalid("Compact JWS must be a string or Uint8Array");
1241
- const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split(".");
1242
- if (length !== 3)
1243
- throw new JWSInvalid("Invalid Compact JWS");
1244
- return verifySignature({ payload, protected: protectedHeader, signature }, shared, key, encodeCompactUnencodedPayload);
1245
- }
1246
-
1247
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/jwt_claims_set.js
1248
- var epoch = (date) => Math.floor(date.getTime() / 1e3);
1249
- var multipliers = {
1250
- s: 1,
1251
- m: 60,
1252
- h: 3600,
1253
- d: 86400,
1254
- w: 604800,
1255
- y: 31557600
1256
- };
1257
- var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
1258
- var checkFailed = "check_failed";
1259
- function invalidDuration() {
1260
- throw new TypeError("Invalid time period format");
1261
- }
1262
- function secs(str) {
1263
- typeof str != "string" && invalidDuration();
1264
- const matched = REGEX.exec(str);
1265
- (!matched || matched[4] && matched[1]) && invalidDuration();
1266
- const value = parseFloat(matched[2]), numericDate2 = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
1267
- return Number.isFinite(numericDate2) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -numericDate2 : numericDate2;
1268
- }
1269
- function validateInput(label, input) {
1270
- if (!Number.isFinite(input))
1271
- throw new TypeError(`Invalid ${label} input`);
1272
- return input;
1273
- }
1274
- var normalizeTyp = (value) => {
1275
- const normalized = value.toLowerCase();
1276
- return value.includes("/") ? normalized : `application/${normalized}`;
1277
- };
1278
- var checkAudiencePresence = (audPayload, audOption) => typeof audPayload == "string" ? audOption.includes(audPayload) : Array.isArray(audPayload) ? audOption.some((aud) => audPayload.includes(aud)) : false;
1279
- function validateNumericDate(payload, claim, required = false) {
1280
- const value = payload[claim];
1281
- if (!(value === void 0 && !required)) {
1282
- if (typeof value != "number")
1283
- throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, payload, claim, "invalid");
1284
- return value;
1285
- }
1286
- }
1287
- function unexpectedClaim(payload, claim) {
1288
- throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, payload, claim, checkFailed);
1289
- }
1290
- function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
1291
- let payload;
1292
- try {
1293
- payload = JSON.parse(strictDecoder.decode(encodedPayload));
1294
- } catch {
1295
- }
1296
- if (!isObject(payload))
1297
- throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
1298
- const { typ } = options;
1299
- if (typ !== void 0 && (typeof protectedHeader.typ != "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ)))
1300
- throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", checkFailed);
1301
- const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options, presenceCheck = [...requiredClaims];
1302
- maxTokenAge !== void 0 && presenceCheck.push("iat"), audience !== void 0 && presenceCheck.push("aud"), subject !== void 0 && presenceCheck.push("sub"), issuer !== void 0 && presenceCheck.push("iss");
1303
- for (const claim of new Set(presenceCheck.reverse()))
1304
- if (!Object.hasOwn(payload, claim))
1305
- throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
1306
- issuer !== void 0 && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss) && unexpectedClaim(payload, "iss"), subject !== void 0 && payload.sub !== subject && unexpectedClaim(payload, "sub"), audience !== void 0 && !checkAudiencePresence(payload.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(payload, "aud");
1307
- const { clockTolerance } = options;
1308
- let tolerance = 0;
1309
- if (typeof clockTolerance == "string")
1310
- tolerance = secs(clockTolerance);
1311
- else if (clockTolerance !== void 0) {
1312
- if (typeof clockTolerance != "number")
1313
- throw new TypeError("Invalid clockTolerance option type");
1314
- tolerance = clockTolerance;
1315
- }
1316
- validateInput("clockTolerance option", tolerance);
1317
- const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === void 0 ? /* @__PURE__ */ new Date() : currentDate)), iat = validateNumericDate(payload, "iat", maxTokenAge !== void 0), nbf = validateNumericDate(payload, "nbf");
1318
- if (nbf !== void 0 && nbf > now + tolerance)
1319
- throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", checkFailed);
1320
- const exp = validateNumericDate(payload, "exp");
1321
- if (exp !== void 0 && exp <= now - tolerance)
1322
- throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", checkFailed);
1323
- if (maxTokenAge !== void 0) {
1324
- const age = now - iat, max = validateInput("maxTokenAge option", typeof maxTokenAge == "number" ? maxTokenAge : secs(maxTokenAge));
1325
- if (age - tolerance > max)
1326
- throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", checkFailed);
1327
- if (age < -tolerance)
1328
- throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", checkFailed);
1329
- }
1330
- return payload;
1331
- }
1332
-
1333
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/jwt/verify.js
1334
- async function jwtVerify(jwt, key, options) {
1335
- const [verified, b64] = await verifyCompact(jwt, prepareVerify(options), key);
1336
- if (!b64)
1337
- throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
1338
- const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options);
1339
- return { ...verified, payload };
1340
- }
1341
-
1342
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/jwks/local.js
1343
- function isUsableJWK(jwk, entry, alg, kid) {
1344
- const { kty, key_ops: keyOps, ext, kid: jwkKid, alg: jwkAlg, use, crv } = jwk;
1345
- return (ext === void 0 || typeof ext == "boolean") && (keyOps === void 0 || Array.isArray(keyOps) && keyOps.every((operation, index) => typeof operation == "string" && keyOps.indexOf(operation) === index) && keyOps.includes("verify")) && entry.kty.includes(kty) && (kid === void 0 || typeof kid == "string" && kid === jwkKid) && (jwkAlg === void 0 ? kty !== "AKP" : alg === jwkAlg) && (use === void 0 || use === "sig") && (!entry.crv || crv === entry.crv);
1346
- }
1347
- async function importWithAlgCache(cache2, jwk, entry) {
1348
- const cached = cache2.get(jwk) || cache2.set(jwk, {}).get(jwk), { alg } = entry;
1349
- if (cached[alg] === void 0) {
1350
- const pending = jwkToKey(entry, jwk, true).then((key) => {
1351
- if (key.type !== "public")
1352
- throw new JWKSInvalid("JSON Web Key Set members must be public keys");
1353
- return cached[alg] = key, key;
1354
- }).catch((error2) => {
1355
- throw cached[alg] === pending && delete cached[alg], error2;
1356
- });
1357
- cached[alg] = pending;
1358
- }
1359
- return cached[alg];
1360
- }
1361
- function createLocalJWKSet(jwks) {
1362
- let snapshot;
1363
- try {
1364
- snapshot = structuredClone(jwks);
1365
- } catch {
1366
- }
1367
- if (!isJwkSet(snapshot))
1368
- throw new JWKSInvalid("JSON Web Key Set malformed");
1369
- const metadata = snapshot.keys.map((jwk) => {
1370
- const normalized = snapshotJwk(jwk);
1371
- return Array.isArray(normalized.key_ops) && (normalized.key_ops = [...normalized.key_ops]), normalized;
1372
- }), cached = /* @__PURE__ */ new WeakMap();
1373
- return Object.defineProperty(async (protectedHeader, token) => {
1374
- const { alg, kid } = { ...protectedHeader, ...token?.header }, entry = typeof alg == "string" ? JWS[alg] : void 0;
1375
- if (!entry || entry.secret)
1376
- throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
1377
- const candidates = snapshot.keys.filter((_, index) => isUsableJWK(metadata[index], entry, alg, kid)), { 0: jwk, length } = candidates;
1378
- if (!length)
1379
- throw new JWKSNoMatchingKey();
1380
- if (length !== 1) {
1381
- const error2 = new JWKSMultipleMatchingKeys();
1382
- throw error2[Symbol.asyncIterator] = async function* () {
1383
- for (const jwk2 of candidates)
1384
- try {
1385
- yield await importWithAlgCache(cached, jwk2, entry);
1386
- } catch {
1387
- }
1388
- }, error2;
1389
- }
1390
- return importWithAlgCache(cached, jwk, entry);
1391
- }, "jwks", {
1392
- value: () => structuredClone(snapshot)
1393
- });
1394
- }
1395
-
1396
- // node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/jwks/remote.js
1397
- function isCloudflareWorkers() {
1398
- return typeof WebSocketPair < "u" || typeof navigator < "u" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime < "u" && EdgeRuntime === "vercel";
1399
- }
1400
- var USER_AGENT;
1401
- (typeof navigator > "u" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) && (USER_AGENT = "jose/v6.2.12");
1402
- var customFetch = /* @__PURE__ */ Symbol();
1403
- async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
1404
- const response = await fetchImpl(url, {
1405
- method: "GET",
1406
- signal,
1407
- redirect: "manual",
1408
- headers
1409
- }).catch((err) => {
1410
- throw err.name === "TimeoutError" ? new JWKSTimeout() : err;
1411
- });
1412
- if (response.status !== 200)
1413
- throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response");
1414
- try {
1415
- return await response.json();
1416
- } catch {
1417
- throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON");
1418
- }
1419
- }
1420
- var jwksCache = /* @__PURE__ */ Symbol();
1421
- function isFreshFor(timestamp, duration) {
1422
- return Number.isFinite(timestamp) && Date.now() < timestamp + duration;
1423
- }
1424
- function validateDuration(value, fallback, option) {
1425
- if (Number.isNaN(value))
1426
- throw new TypeError(`"${option}" option must not be NaN`);
1427
- return typeof value == "number" ? value : fallback;
1428
- }
1429
- function createRemoteJWKSet(url, options) {
1430
- if (!(url instanceof URL))
1431
- throw new TypeError("url must be an instance of URL");
1432
- const href = new URL(url.href).href, opts = options ?? {}, timeoutOption = opts.timeoutDuration;
1433
- if (typeof timeoutOption == "number" && (!Number.isInteger(timeoutOption) || timeoutOption < 0))
1434
- throw new TypeError('"timeoutDuration" option must be a non-negative integer');
1435
- const timeoutDuration = typeof timeoutOption == "number" ? timeoutOption : 5e3, cooldownDuration = validateDuration(opts.cooldownDuration, 3e4, "cooldownDuration"), cacheMaxAge = validateDuration(opts.cacheMaxAge, 6e5, "cacheMaxAge"), headers = new Headers(opts.headers);
1436
- USER_AGENT && !headers.has("User-Agent") && headers.set("User-Agent", USER_AGENT), headers.has("accept") || headers.set("accept", "application/json, application/jwk-set+json");
1437
- const fetchImpl = opts[customFetch], cache2 = opts[jwksCache];
1438
- let jwksTimestamp, pendingFetch, reloadSequence = 0, appliedSequence = 0, local;
1439
- if (cache2 && typeof cache2 == "object") {
1440
- const { uat, jwks } = cache2;
1441
- isFreshFor(uat, cacheMaxAge) && isJwkSet(jwks) && (jwksTimestamp = uat, local = createLocalJWKSet(jwks));
1442
- }
1443
- const reload = async () => {
1444
- 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);
1447
- if (sequence <= appliedSequence)
1448
- return;
1449
- local = next;
1450
- const updatedAt = Date.now();
1451
- cache2 && (cache2.uat = updatedAt, cache2.jwks = json2), jwksTimestamp = updatedAt, appliedSequence = sequence;
1452
- }).finally(() => {
1453
- pendingFetch === current && (pendingFetch = void 0);
1454
- });
1455
- }
1456
- await pendingFetch;
1457
- };
1458
- return Object.defineProperties(async (protectedHeader, token) => {
1459
- (!local || !isFreshFor(jwksTimestamp, cacheMaxAge)) && await reload();
1460
- try {
1461
- return await local(protectedHeader, token);
1462
- } catch (err) {
1463
- if (err instanceof JWKSNoMatchingKey && !isFreshFor(jwksTimestamp, cooldownDuration))
1464
- return await reload(), local(protectedHeader, token);
1465
- throw err;
1466
- }
1467
- }, {
1468
- coolingDown: {
1469
- get: () => isFreshFor(jwksTimestamp, cooldownDuration),
1470
- enumerable: true
1471
- },
1472
- fresh: {
1473
- get: () => isFreshFor(jwksTimestamp, cacheMaxAge),
1474
- enumerable: true
1475
- },
1476
- reload: {
1477
- value: reload,
1478
- enumerable: true
1479
- },
1480
- reloading: {
1481
- get: () => !!pendingFetch,
1482
- enumerable: true
1483
- },
1484
- jwks: {
1485
- value: () => local?.jwks(),
1486
- enumerable: true
1487
- }
1488
- });
1489
- }
1490
-
1491
- // packages/cli/src/web/team-auth.ts
1492
1603
  var scrypt = promisify(scryptCallback);
1493
1604
  var COOKIE = "zero_team_session";
1494
1605
  var SSO_COOKIE = "zero_team_sso";
@@ -1510,8 +1621,8 @@ function parseTeamConfig(input) {
1510
1621
  return configSchema.parse(input);
1511
1622
  }
1512
1623
  var TeamAuthError = class extends Error {
1513
- constructor(message2, statusCode = 401) {
1514
- super(message2);
1624
+ constructor(message, statusCode = 401) {
1625
+ super(message);
1515
1626
  this.statusCode = statusCode;
1516
1627
  }
1517
1628
  statusCode;
@@ -1534,8 +1645,8 @@ function parseCookies(header) {
1534
1645
  return cookies;
1535
1646
  }
1536
1647
  function getSessionId(req) {
1537
- const id = parseCookies(req.headers.cookie)[COOKIE];
1538
- return id && /^[\w-]{43}$/.test(id) ? id : void 0;
1648
+ const id2 = parseCookies(req.headers.cookie)[COOKIE];
1649
+ return id2 && /^[\w-]{43}$/.test(id2) ? id2 : void 0;
1539
1650
  }
1540
1651
  async function hashTeamPassword(password) {
1541
1652
  if (!password || password.length > 1024) throw new TeamAuthError("Password must contain 1\u20131024 characters", 400);
@@ -1592,8 +1703,8 @@ var TeamAuth = class {
1592
1703
  cookie(name, value, seconds) {
1593
1704
  return `${name}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${seconds}${this.origin.startsWith("https:") ? "; Secure" : ""}`;
1594
1705
  }
1595
- lookupUser(id) {
1596
- const user = this.config?.users.find((row) => row.id === id);
1706
+ lookupUser(id2) {
1707
+ const user = this.config?.users.find((row) => row.id === id2);
1597
1708
  return user ? { workspaceId: this.config.workspace.id, userId: user.id, displayName: user.name, role: user.role } : null;
1598
1709
  }
1599
1710
  /** Trusted server configuration only; never serialize this through a browser API. */
@@ -1617,11 +1728,11 @@ var TeamAuth = class {
1617
1728
  this.config.users.push(member);
1618
1729
  return this.lookupUser(member.id);
1619
1730
  }
1620
- resolveSessionId(id) {
1621
- if (!id) return null;
1622
- const session = this.sessions.get(id);
1731
+ resolveSessionId(id2) {
1732
+ if (!id2) return null;
1733
+ const session = this.sessions.get(id2);
1623
1734
  if (!session || session.expires <= this.now()) {
1624
- this.sessions.delete(id);
1735
+ this.sessions.delete(id2);
1625
1736
  return null;
1626
1737
  }
1627
1738
  return this.lookupUser(session.userId);
@@ -1633,7 +1744,7 @@ var TeamAuth = class {
1633
1744
  return { enabled: this.enabled, ...this.config ? { workspace: this.config.workspace } : {}, user: this.resolveSession(req), sso: !!this.config?.oidc };
1634
1745
  }
1635
1746
  issueSession(userId) {
1636
- for (const [id, session] of this.sessions) if (session.expires <= this.now()) this.sessions.delete(id);
1747
+ for (const [id2, session] of this.sessions) if (session.expires <= this.now()) this.sessions.delete(id2);
1637
1748
  if (this.sessions.size >= 1e4) throw new TeamAuthError("Session capacity reached", 503);
1638
1749
  const sessionId = opaque();
1639
1750
  this.sessions.set(sessionId, { userId, expires: this.now() + SESSION_MS });
@@ -1644,7 +1755,7 @@ var TeamAuth = class {
1644
1755
  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
1756
  if (!parsed.success) throw new TeamAuthError("User ID and password are required", 400);
1646
1757
  const key = rateKey;
1647
- for (const [id, row] of this.attempts) if (row.expires <= this.now()) this.attempts.delete(id);
1758
+ for (const [id2, row] of this.attempts) if (row.expires <= this.now()) this.attempts.delete(id2);
1648
1759
  const attempt = this.attempts.get(key) ?? { count: 0, expires: this.now() + 6e4 };
1649
1760
  if (attempt.count >= 10 || this.attempts.size >= 1e4) throw new TeamAuthError("Too many sign-in attempts; try again shortly", 429);
1650
1761
  attempt.count++;
@@ -1655,8 +1766,8 @@ var TeamAuth = class {
1655
1766
  return this.issueSession(user.id);
1656
1767
  }
1657
1768
  logout(req) {
1658
- const id = getSessionId(req);
1659
- if (id) this.sessions.delete(id);
1769
+ const id2 = getSessionId(req);
1770
+ if (id2) this.sessions.delete(id2);
1660
1771
  return { setCookie: this.cookie(COOKIE, "", 0) };
1661
1772
  }
1662
1773
  requireProviderURL(value) {
@@ -1680,7 +1791,7 @@ var TeamAuth = class {
1680
1791
  async beginSSO() {
1681
1792
  const discovery = await this.getDiscovery();
1682
1793
  const oidc = this.config.oidc;
1683
- for (const [id, pending] of this.pending) if (pending.expires <= this.now()) this.pending.delete(id);
1794
+ for (const [id2, pending] of this.pending) if (pending.expires <= this.now()) this.pending.delete(id2);
1684
1795
  if (this.pending.size >= 1e3) throw new TeamAuthError("Too many pending sign-ins", 429);
1685
1796
  const state = opaque(), nonce = opaque(), verifier = opaque(), browser = opaque();
1686
1797
  this.pending.set(state, { nonce, verifier, browser, expires: this.now() + SSO_MS });
@@ -1719,8 +1830,8 @@ var TeamAuth = class {
1719
1830
 
1720
1831
  // packages/cli/src/web/findings-access-tokens.ts
1721
1832
  var FindingsAccessError = class extends Error {
1722
- constructor(message2, statusCode = 400) {
1723
- super(message2);
1833
+ constructor(message, statusCode = 400) {
1834
+ super(message);
1724
1835
  this.statusCode = statusCode;
1725
1836
  this.name = "FindingsAccessError";
1726
1837
  }
@@ -1739,7 +1850,7 @@ var publicTokenSchema = external_exports.object({
1739
1850
  }).strict();
1740
1851
  var tokenSchema = publicTokenSchema.extend({ tokenHash: external_exports.string().regex(/^[a-f0-9]{64}$/) }).strict();
1741
1852
  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({
1853
+ var createSchema2 = external_exports.object({
1743
1854
  name: external_exports.string().trim().min(1).max(160),
1744
1855
  scopes: scopesSchema.optional(),
1745
1856
  expiresAt: external_exports.string().datetime().nullable().optional()
@@ -1788,7 +1899,7 @@ var FindingsAccessTokenStore = class {
1788
1899
  #write(records) {
1789
1900
  const state = stateSchema.safeParse({ schemaVersion: 1, workspaceId: this.workspaceId, tokens: records });
1790
1901
  if (!state.success) throw new FindingsAccessError("API credential limit reached.", 409);
1791
- const temporary = this.#path + "." + randomUUID2() + ".tmp";
1902
+ const temporary = this.#path + "." + randomUUID3() + ".tmp";
1792
1903
  try {
1793
1904
  writeFileSync(temporary, JSON.stringify(state.data), { flag: "wx", mode: 384 });
1794
1905
  renameSync(temporary, this.#path);
@@ -1822,22 +1933,22 @@ var FindingsAccessTokenStore = class {
1822
1933
  /** Caller supplies a verified owner identity, never createdBy from the HTTP body. */
1823
1934
  create(input, createdBy) {
1824
1935
  return this.#mutate(() => {
1825
- const parsed = createSchema.safeParse(input);
1936
+ const parsed = createSchema2.safeParse(input);
1826
1937
  if (!parsed.success || !createdBy || createdBy.length > 256) throw new FindingsAccessError("Use a name, optional read:findings scope and future expiry.");
1827
1938
  const records = this.#read();
1828
1939
  const now = this.#now();
1829
1940
  const expiresAt = parsed.data.expiresAt === void 0 ? new Date(now + 90 * 864e5).toISOString() : parsed.data.expiresAt;
1830
1941
  if (expiresAt !== null && Date.parse(expiresAt) <= now) throw new FindingsAccessError("API credential expiry must be in the future.");
1831
1942
  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") };
1943
+ 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
1944
  this.#write([...records, record]);
1834
1945
  return { credential: redact(record), token };
1835
1946
  });
1836
1947
  }
1837
- revoke(id) {
1948
+ revoke(id2) {
1838
1949
  return this.#mutate(() => {
1839
1950
  const records = this.#read();
1840
- const record = records.find((token) => token.id === id);
1951
+ const record = records.find((token) => token.id === id2);
1841
1952
  if (!record) throw new FindingsAccessError("API credential not found.", 404);
1842
1953
  record.revokedAt ??= new Date(this.#now()).toISOString();
1843
1954
  this.#write(records);
@@ -1894,12 +2005,12 @@ var FindingsReadApi = class {
1894
2005
  this.#dbPath = resolveOsecDbPath(options.dbPath);
1895
2006
  this.workspaceId = options.workspaceId;
1896
2007
  }
1897
- #item(db, id) {
1898
- const row = db.getFinding(id);
2008
+ #item(db, id2) {
2009
+ const row = db.getFinding(id2);
1899
2010
  if (!row) throw new FindingsAccessError("Finding not found.", 404);
1900
2011
  const scan = db.getScan(row.scanId);
1901
2012
  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) }) };
2013
+ return { scanId: row.scanId, target: scan.target, finding: findingFromRow(row, { ...db.getFindingReviewFields(id2) }) };
1903
2014
  }
1904
2015
  list(query) {
1905
2016
  const raw = {};
@@ -1934,11 +2045,11 @@ var FindingsReadApi = class {
1934
2045
  db.close();
1935
2046
  }
1936
2047
  }
1937
- detail(id) {
1938
- if (!boundedId.safeParse(id).success) throw new FindingsAccessError("Invalid finding ID.");
2048
+ detail(id2) {
2049
+ if (!boundedId.safeParse(id2).success) throw new FindingsAccessError("Invalid finding ID.");
1939
2050
  const db = new osecDB(this.#dbPath, { readOnly: true });
1940
2051
  try {
1941
- return { schemaVersion: 1, workspaceId: this.workspaceId, ...this.#item(db, id) };
2052
+ return { schemaVersion: 1, workspaceId: this.workspaceId, ...this.#item(db, id2) };
1942
2053
  } finally {
1943
2054
  db.close();
1944
2055
  }
@@ -1946,7 +2057,7 @@ var FindingsReadApi = class {
1946
2057
  export(query) {
1947
2058
  if ([...query.keys()].some((key) => key !== "id")) throw new FindingsAccessError("Use repeated id parameters to select export findings.");
1948
2059
  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.");
2060
+ 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
2061
  return { schemaVersion: 1, workspaceId: this.workspaceId, coverage: "selected-retained-findings", report: retainedFindingSnapshot(ids, this.#dbPath) };
1951
2062
  }
1952
2063
  };
@@ -2023,14 +2134,17 @@ function engagementMarkdown(report) {
2023
2134
  return lines.filter((line, index) => line !== "" || lines[index - 1] !== "").join("\n");
2024
2135
  }
2025
2136
 
2137
+ // packages/cli/src/web/workspace-services.ts
2138
+ import { randomUUID as randomUUID6 } from "node:crypto";
2139
+
2026
2140
  // packages/cli/src/web/skills.ts
2027
- import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
2141
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
2028
2142
  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
2143
  import { join as join2, resolve as resolve2 } from "node:path";
2030
2144
  import { homedir as homedir2 } from "node:os";
2031
2145
  var SkillsError = class extends Error {
2032
- constructor(message2, statusCode = 400) {
2033
- super(message2);
2146
+ constructor(message, statusCode = 400) {
2147
+ super(message);
2034
2148
  this.statusCode = statusCode;
2035
2149
  }
2036
2150
  statusCode;
@@ -2121,12 +2235,12 @@ var SkillsStore = class _SkillsStore {
2121
2235
  #discovery() {
2122
2236
  return discoverAgentSkills(this.discoveryOptions());
2123
2237
  }
2124
- #builtin(id) {
2125
- const builtinId = id.startsWith("builtin/") ? id.slice(8) : id.includes("/") ? void 0 : id;
2238
+ #builtin(id2) {
2239
+ const builtinId = id2.startsWith("builtin/") ? id2.slice(8) : id2.includes("/") ? void 0 : id2;
2126
2240
  if (!builtinId) return;
2127
2241
  const skill = loadSkillRegistry().get(builtinId);
2128
2242
  if (!skill) return;
2129
- id = `builtin/${builtinId}`;
2243
+ id2 = `builtin/${builtinId}`;
2130
2244
  const name = skill.id;
2131
2245
  const content = `---
2132
2246
  name: ${name}
@@ -2136,20 +2250,20 @@ description: ${JSON.stringify(skill.description)}
2136
2250
  ${skill.content}
2137
2251
  `;
2138
2252
  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 };
2253
+ return { metadata: { id: id2, name, description: skill.description, scope: "builtin", revision: revision(files), writable: false, fileCount: 1, source: "Built in" }, files };
2140
2254
  }
2141
- #entry(id) {
2142
- const built = this.#builtin(id);
2255
+ #entry(id2) {
2256
+ const built = this.#builtin(id2);
2143
2257
  if (built) return built;
2144
- const skill = this.#discovery().skills.find((row) => row.id === id);
2258
+ const skill = this.#discovery().skills.find((row) => row.id === id2);
2145
2259
  if (!skill) throw new SkillsError("Skill not found.", 404);
2146
2260
  return this.#describe(skill);
2147
2261
  }
2148
2262
  #describe(skill) {
2149
- const id = skill.id;
2263
+ const id2 = skill.id;
2150
2264
  const files = readFiles(skill.directory);
2151
2265
  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 };
2266
+ 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
2267
  }
2154
2268
  #access(metadata, access = {}) {
2155
2269
  return { ...metadata, writable: access.canWrite !== false && metadata.scope !== "builtin" && (metadata.scope === "workspace" || access.allowHostEdits === true) };
@@ -2168,12 +2282,12 @@ ${skill.content}
2168
2282
  for (const skill of loadSkillRegistry().values()) skills.push(this.#builtin(`builtin/${skill.id}`).metadata);
2169
2283
  return { skills, mounts: this.mounts(), diagnostics, workspacePath: this.workspacePath };
2170
2284
  }
2171
- get(id, access = {}) {
2172
- const row = this.#entry(id);
2285
+ get(id2, access = {}) {
2286
+ const row = this.#entry(id2);
2173
2287
  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
2288
  }
2175
- export(id) {
2176
- const row = this.#entry(id);
2289
+ export(id2) {
2290
+ const row = this.#entry(id2);
2177
2291
  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
2292
  }
2179
2293
  #validate(content, name) {
@@ -2241,7 +2355,7 @@ ${skill.content}
2241
2355
  } catch (cause) {
2242
2356
  if (cause.code !== "ENOENT") throw cause;
2243
2357
  }
2244
- const temporary = join2(this.#state, `.import-${randomUUID3()}`);
2358
+ const temporary = join2(this.#state, `.import-${randomUUID4()}`);
2245
2359
  mkdirSync2(temporary, { mode: 448 });
2246
2360
  try {
2247
2361
  for (const file of files) {
@@ -2280,31 +2394,31 @@ ${skill.content}
2280
2394
  if (!names.has("SKILL.md")) throw new SkillsError("Skill bundle needs SKILL.md.");
2281
2395
  return this.get(this.#write(files, bundle.name));
2282
2396
  }
2283
- update(id, input, access = {}) {
2284
- return this.#transaction(() => this.#update(id, input, access));
2397
+ update(id2, input, access = {}) {
2398
+ return this.#transaction(() => this.#update(id2, input, access));
2285
2399
  }
2286
- #update(id, input, access) {
2400
+ #update(id2, input, access) {
2287
2401
  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);
2402
+ const row = this.#entry(id2);
2289
2403
  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
2404
  if (row.metadata.revision !== expectedRevision) throw new SkillsError("This skill changed. Reload it before saving.", 409);
2291
2405
  this.#validate(content, row.metadata.name);
2292
2406
  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
2407
  const target = join2(row.directory, "SKILL.md");
2294
- const pending = join2(row.directory, `.skill-edit-${randomUUID3()}.tmp`);
2408
+ const pending = join2(row.directory, `.skill-edit-${randomUUID4()}.tmp`);
2295
2409
  try {
2296
2410
  writeFileSync2(pending, content, { flag: "wx", mode: lstatSync2(target).mode & 511 });
2297
2411
  renameSync2(pending, target);
2298
2412
  } finally {
2299
2413
  rmSync(pending, { force: true });
2300
2414
  }
2301
- return this.get(id, access);
2415
+ return this.get(id2, access);
2302
2416
  }
2303
- copy(id, input, access = {}) {
2417
+ copy(id2, input, access = {}) {
2304
2418
  if (access.canWrite === false) throw new SkillsError("This account has read-only workspace access.", 403);
2305
2419
  return this.#transaction(() => {
2306
2420
  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);
2421
+ const row = this.#entry(id2);
2308
2422
  if (row.metadata.revision !== expectedRevision) throw new SkillsError("This skill changed. Reload it before copying.", 409);
2309
2423
  const { name } = this.#validate(content);
2310
2424
  const files = row.files.map((file) => file.path === "SKILL.md" ? { path: file.path, bytes: Buffer.from(content) } : file);
@@ -2319,18 +2433,18 @@ ${skill.content}
2319
2433
  };
2320
2434
  return {
2321
2435
  list: () => this.list(access),
2322
- read: (id) => this.get(id, access),
2436
+ read: (id2) => this.get(id2, access),
2323
2437
  create: (content) => {
2324
2438
  assertWrite();
2325
2439
  return this.create({ content });
2326
2440
  },
2327
- update: (id, input) => {
2441
+ update: (id2, input) => {
2328
2442
  assertWrite();
2329
- return this.update(id, input, access);
2443
+ return this.update(id2, input, access);
2330
2444
  },
2331
- copy: (id, input) => {
2445
+ copy: (id2, input) => {
2332
2446
  assertWrite();
2333
- return this.copy(id, input, access);
2447
+ return this.copy(id2, input, access);
2334
2448
  }
2335
2449
  };
2336
2450
  }
@@ -2349,24 +2463,24 @@ ${skill.content}
2349
2463
  const mounts = this.mounts();
2350
2464
  if (mounts.length >= 100) throw new SkillsError("At most 100 folders can be mounted.", 409);
2351
2465
  if (mounts.some((item) => item.path === canonical)) throw new SkillsError("This folder is already mounted.", 409);
2352
- const mount = { id: randomUUID3(), path: canonical };
2466
+ const mount = { id: randomUUID4(), path: canonical };
2353
2467
  const found = discoverAgentSkills({ homeDir: null, mounts: [mount] });
2354
2468
  if (!found.skills.length) throw new SkillsError("No valid SKILL.md folders found.");
2355
2469
  mounts.push(mount);
2356
2470
  this.#saveMounts(mounts);
2357
2471
  return mount;
2358
2472
  }
2359
- unmount(id) {
2360
- return this.#transaction(() => this.#unmount(id));
2473
+ unmount(id2) {
2474
+ return this.#transaction(() => this.#unmount(id2));
2361
2475
  }
2362
- #unmount(id) {
2476
+ #unmount(id2) {
2363
2477
  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));
2478
+ if (!mounts.some((item) => item.id === id2)) throw new SkillsError("Mount not found.", 404);
2479
+ this.#saveMounts(mounts.filter((item) => item.id !== id2));
2366
2480
  }
2367
2481
  #saveMounts(mounts) {
2368
2482
  mkdirSync2(this.#state, { recursive: true, mode: 448 });
2369
- const temporary = join2(this.#state, `.mounts-${randomUUID3()}`);
2483
+ const temporary = join2(this.#state, `.mounts-${randomUUID4()}`);
2370
2484
  try {
2371
2485
  writeFileSync2(temporary, JSON.stringify(mounts), { flag: "wx", mode: 384 });
2372
2486
  renameSync2(temporary, join2(this.#state, "mounts.json"));
@@ -2377,12 +2491,12 @@ ${skill.content}
2377
2491
  };
2378
2492
 
2379
2493
  // packages/cli/src/web/engagements.ts
2380
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
2494
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
2381
2495
  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
2496
  import { join as join3 } from "node:path";
2383
2497
  var EngagementError = class extends Error {
2384
- constructor(message2, statusCode = 400) {
2385
- super(message2);
2498
+ constructor(message, statusCode = 400) {
2499
+ super(message);
2386
2500
  this.statusCode = statusCode;
2387
2501
  this.name = "EngagementError";
2388
2502
  }
@@ -2438,7 +2552,7 @@ var EngagementStore = class {
2438
2552
  const body = JSON.stringify({ schemaVersion: 1, workspace: this.workspace, engagements });
2439
2553
  if (Buffer.byteLength(body) > MAX_STATE_BYTES) throw new EngagementError("Engagement storage limit reached.", 409);
2440
2554
  mkdirSync3(this.#directory, { recursive: true, mode: 448 });
2441
- const temporary = `${this.#path}.${randomUUID4()}.tmp`;
2555
+ const temporary = `${this.#path}.${randomUUID5()}.tmp`;
2442
2556
  try {
2443
2557
  writeFileSync3(temporary, body, { flag: "wx", mode: 384 });
2444
2558
  renameSync3(temporary, this.#path);
@@ -2454,7 +2568,7 @@ var EngagementStore = class {
2454
2568
  if (!ids.length) return;
2455
2569
  const db = new osecDB(this.#dbPath);
2456
2570
  try {
2457
- for (const id of ids) if (!db.getScan(id)) throw new EngagementError(`Selected scan was not found: ${id}`, 404);
2571
+ for (const id2 of ids) if (!db.getScan(id2)) throw new EngagementError(`Selected scan was not found: ${id2}`, 404);
2458
2572
  } finally {
2459
2573
  db.close();
2460
2574
  }
@@ -2514,8 +2628,8 @@ var EngagementStore = class {
2514
2628
  list() {
2515
2629
  return this.#read().sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
2516
2630
  }
2517
- get(id) {
2518
- const row = this.#read().find((item) => item.id === id);
2631
+ get(id2) {
2632
+ const row = this.#read().find((item) => item.id === id2);
2519
2633
  if (!row) throw new EngagementError("Engagement not found in this workspace.", 404);
2520
2634
  return row;
2521
2635
  }
@@ -2526,17 +2640,17 @@ var EngagementStore = class {
2526
2640
  return this.#transaction((rows) => {
2527
2641
  if (rows.length >= 200) throw new EngagementError("Engagement limit reached.", 409);
2528
2642
  const now = (/* @__PURE__ */ new Date()).toISOString();
2529
- const row = { ...fields, id: randomUUID4(), revision: 1, createdAt: now, updatedAt: now, ...attribution ? { createdBy: attribution, updatedBy: attribution } : {} };
2643
+ const row = { ...fields, id: randomUUID5(), revision: 1, createdAt: now, updatedAt: now, ...attribution ? { createdBy: attribution, updatedBy: attribution } : {} };
2530
2644
  rows.push(row);
2531
2645
  return structuredClone(row);
2532
2646
  });
2533
2647
  }
2534
- update(id, input, actor) {
2648
+ update(id2, input, actor) {
2535
2649
  const { expectedRevision, ...patch } = parse2(updateSchema, input);
2536
2650
  const attribution = actor ? parse2(actorSchema, actor) : void 0;
2537
2651
  if (patch.scanIds) this.#validateScans(patch.scanIds);
2538
2652
  return this.#transaction((rows) => {
2539
- const index = rows.findIndex((row2) => row2.id === id);
2653
+ const index = rows.findIndex((row2) => row2.id === id2);
2540
2654
  if (index < 0) throw new EngagementError("Engagement not found in this workspace.", 404);
2541
2655
  if (attribution && expectedRevision === void 0) throw new EngagementError("Review the latest report before saving. Your draft has been kept.", 409);
2542
2656
  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 +2659,8 @@ var EngagementStore = class {
2545
2659
  return structuredClone(row);
2546
2660
  });
2547
2661
  }
2548
- report(id) {
2549
- const engagement = this.get(id);
2662
+ report(id2) {
2663
+ const engagement = this.get(id2);
2550
2664
  const db = new osecDB(this.#dbPath);
2551
2665
  try {
2552
2666
  const sources = engagement.scanIds.map((scanId) => {
@@ -2568,22 +2682,22 @@ function reviewStatus(finding) {
2568
2682
  function assembleEngagementReport(engagement, sources) {
2569
2683
  const record = parse2(recordSchema, engagement);
2570
2684
  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);
2685
+ 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
2686
  const groups = /* @__PURE__ */ new Map();
2573
2687
  let findingCount = 0;
2574
- const scans = record.scanIds.map((id) => {
2575
- const source = byScan.get(id);
2688
+ const scans = record.scanIds.map((id2) => {
2689
+ const source = byScan.get(id2);
2576
2690
  for (const finding of source.report.findings) {
2577
2691
  findingCount++;
2578
2692
  const fingerprint = finding.fingerprint?.trim();
2579
- const key = fingerprint ? `fingerprint:${fingerprint}` : `source:${id}:${finding.id}`;
2693
+ const key = fingerprint ? `fingerprint:${fingerprint}` : `source:${id2}:${finding.id}`;
2580
2694
  const status = reviewStatus(finding);
2581
2695
  let group = groups.get(key);
2582
2696
  if (!group) {
2583
2697
  group = { key, ...fingerprint ? { fingerprint } : {}, reviewStatus: status, occurrences: [] };
2584
2698
  groups.set(key, group);
2585
2699
  } else if (group.reviewStatus !== status) group.reviewStatus = "unreviewed";
2586
- group.occurrences.push({ scanId: id, findingId: finding.id, finding: structuredClone(finding) });
2700
+ group.occurrences.push({ scanId: id2, findingId: finding.id, finding: structuredClone(finding) });
2587
2701
  }
2588
2702
  return { ...source.scan, warnings: structuredClone(source.report.warnings), executionSuccessful: source.report.executionSuccessful, exitReason: source.report.exitReason, ...source.report.error ? { error: source.report.error } : {} };
2589
2703
  });
@@ -2606,8 +2720,8 @@ function assembleEngagementReport(engagement, sources) {
2606
2720
  }
2607
2721
 
2608
2722
  // 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();
2723
+ 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();
2724
+ 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
2725
  var PREFIX = "/api/console/workflow-triggers";
2612
2726
  var terminal = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted"]);
2613
2727
  var WorkflowTriggerService = class {
@@ -2632,10 +2746,10 @@ var WorkflowTriggerService = class {
2632
2746
  async handle(method, url, input) {
2633
2747
  if (url.pathname !== PREFIX && !url.pathname.startsWith(`${PREFIX}/`)) return null;
2634
2748
  try {
2635
- const id = url.pathname.slice(PREFIX.length + 1);
2749
+ const id2 = url.pathname.slice(PREFIX.length + 1);
2636
2750
  if (url.pathname === PREFIX && method === "GET") return { status: 200, data: { triggers: this.#store.list(url.searchParams.get("workflowId") ?? void 0).map(publicTrigger) } };
2637
2751
  if (url.pathname === PREFIX && method === "POST") {
2638
- const parsed = createSchema2.parse(input);
2752
+ const parsed = createSchema3.parse(input);
2639
2753
  try {
2640
2754
  new Intl.DateTimeFormat("en", { timeZone: parsed.timezone });
2641
2755
  } catch {
@@ -2654,22 +2768,42 @@ var WorkflowTriggerService = class {
2654
2768
  }
2655
2769
  return { status: 201, data: { trigger: publicTrigger(this.#store.create({ ...fields, reviewedConnectionIdentity: identity?.connectionIdentity, reviewedModel: identity?.model, reviewedProviderId: identity?.providerId })) } };
2656
2770
  }
2657
- const trigger = this.#store.get(id);
2771
+ const trigger = this.#store.get(id2);
2658
2772
  if (!trigger) return error(404, "Schedule not found.");
2659
2773
  if (method === "PATCH") {
2660
2774
  const patch = toggleSchema.parse(input);
2775
+ if (patch.expectedVersion !== void 0 && patch.expectedVersion !== (trigger.version ?? 1)) return error(409, "Schedule changed. Review it again before enabling.");
2776
+ 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.");
2777
+ const changedTiming = patch.cadence !== void 0 || patch.startAt !== void 0 || patch.timezone !== void 0 || patch.workflowRevision !== void 0;
2778
+ const enabled = patch.enabled ?? (changedTiming ? false : trigger.enabled);
2779
+ const next = { ...trigger, ...patch, enabled };
2780
+ try {
2781
+ new Intl.DateTimeFormat("en", { timeZone: next.timezone });
2782
+ } catch {
2783
+ return error(400, "Choose a valid timezone.");
2784
+ }
2661
2785
  let identity = void 0;
2662
- if (patch.enabled) {
2786
+ if (enabled) {
2663
2787
  if (!patch.approval) return error(400, "Review this schedule before enabling it.");
2664
2788
  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);
2789
+ if (definition?.revision !== next.workflowRevision) return error(409, "Workflow changed. Review its current revision before scheduling.");
2790
+ identity = await this.#adapter.validate(next);
2791
+ if (!changedTiming) this.#verifyIdentity(trigger, identity);
2668
2792
  }
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)) } };
2793
+ return { status: 200, data: { trigger: publicTrigger(this.#store.update(id2, {
2794
+ cadence: next.cadence,
2795
+ startAt: next.startAt,
2796
+ timezone: next.timezone,
2797
+ workflowRevision: next.workflowRevision,
2798
+ ...enabled ? { reviewedModel: identity?.model, reviewedProviderId: identity?.providerId, reviewedConnectionIdentity: identity?.connectionIdentity } : {},
2799
+ enabled,
2800
+ lastStatus: enabled ? "scheduled" : "paused",
2801
+ lastError: void 0,
2802
+ nextFireAt: nextWorkflowTriggerFire(next.startAt, next.cadence, Date.now())
2803
+ }, enabled ? trigger.version ?? 1 : patch.expectedVersion)) } };
2670
2804
  }
2671
2805
  if (method === "DELETE") {
2672
- if (!this.#store.delete(id)) return error(409, "Pause the schedule and wait for its active execution before deleting.");
2806
+ if (!this.#store.delete(id2)) return error(409, "Pause the schedule and wait for its active execution before deleting.");
2673
2807
  return { status: 200, data: { deleted: true } };
2674
2808
  }
2675
2809
  return error(405, "Unsupported schedule action.");
@@ -2677,6 +2811,43 @@ var WorkflowTriggerService = class {
2677
2811
  return error(cause instanceof external_exports.ZodError ? 400 : 409, cause instanceof Error ? cause.message.slice(0, 1e3) : "Schedule could not be updated.");
2678
2812
  }
2679
2813
  }
2814
+ /** Chat transport shares the same validation and scheduler as the HTTP editor. */
2815
+ async invokeLifecycle(sessionId, name, args, context) {
2816
+ const identifier = external_exports.string().min(1).max(160);
2817
+ if (name === "list_schedules") {
2818
+ const request = external_exports.object({ workflowId: identifier.optional() }).strict().parse(args);
2819
+ if (context?.workflowId && request.workflowId && context.workflowId !== request.workflowId) throw new Error("This editor is bound to another workflow.");
2820
+ return { schedules: this.#store.list(context?.workflowId ?? request.workflowId).filter((trigger) => context?.workflowId || trigger.sessionId === sessionId).map(publicTrigger) };
2821
+ }
2822
+ let method;
2823
+ let path = PREFIX;
2824
+ let input;
2825
+ if (name === "create_schedule") {
2826
+ const request = createSchema3.omit({ sessionId: true, approval: true }).extend({ enabled: external_exports.boolean().default(false) }).parse(args);
2827
+ if (context?.workflowId && context.workflowId !== request.workflowId) throw new Error("This editor is bound to another workflow.");
2828
+ if (request.enabled && !context?.activationApproved) throw new Error("Schedule activation requires operator approval.");
2829
+ method = "POST";
2830
+ input = { ...request, sessionId, ...request.enabled ? { approval: "enable-reviewed-trigger" } : {} };
2831
+ } else if (name === "update_schedule" || name === "delete_schedule") {
2832
+ const { id: id2, ...fields } = external_exports.object({ id: identifier }).passthrough().parse(args);
2833
+ const trigger = this.#store.get(id2);
2834
+ if (!trigger || (context?.workflowId ? trigger.workflowId !== context.workflowId : trigger.sessionId !== sessionId)) throw new Error("Schedule not found for this editor.");
2835
+ path += `/${encodeURIComponent(id2)}`;
2836
+ if (name === "delete_schedule") {
2837
+ external_exports.object({}).strict().parse(fields);
2838
+ method = "DELETE";
2839
+ input = void 0;
2840
+ } else {
2841
+ const patch = toggleSchema.parse(fields);
2842
+ if (patch.enabled && !context?.activationApproved) throw new Error("Schedule activation requires operator approval.");
2843
+ method = "PATCH";
2844
+ input = { ...patch, ...patch.enabled ? { approval: "enable-reviewed-trigger" } : {} };
2845
+ }
2846
+ } else throw new Error("Unknown schedule action.");
2847
+ const result = await this.handle(method, new URL(path, "http://localhost"), input);
2848
+ if (!result || result.status >= 400) throw new Error(result?.data?.error ?? "Schedule could not be updated.");
2849
+ return result.data;
2850
+ }
2680
2851
  async tick(now = Date.now()) {
2681
2852
  if (this.#closed || this.#polling) return;
2682
2853
  this.#polling = true;
@@ -2732,8 +2903,8 @@ var WorkflowTriggerService = class {
2732
2903
  this.#workflows.close();
2733
2904
  }
2734
2905
  };
2735
- function error(status, message2) {
2736
- return { status, data: { error: message2 } };
2906
+ function error(status, message) {
2907
+ return { status, data: { error: message } };
2737
2908
  }
2738
2909
  function isProcessAlive(pid) {
2739
2910
  try {
@@ -2852,7 +3023,10 @@ var GitHubPublicationAuth = class {
2852
3023
  // packages/cli/src/web/workspace-services.ts
2853
3024
  async function createWorkspaceServices(options) {
2854
3025
  const skills = new SkillsStore({ workspace: options.workspace ?? process.cwd(), stateDir: options.stateDir ?? homeStateDir(), team: options.auth.enabled });
3026
+ const remoteProviderRelays = new RemoteProviderRelayBroker({ epoch: options.relayEpoch ?? randomUUID6() });
2855
3027
  const gateway = new ConsoleGateway({
3028
+ allowChatGptPlan: !options.auth.enabled,
3029
+ remoteProviderRelays,
2856
3030
  dbPath: options.dbPath,
2857
3031
  projectPath: options.workspace,
2858
3032
  skillAuthoring: (author, workspaceRoot) => {
@@ -2863,13 +3037,13 @@ async function createWorkspaceServices(options) {
2863
3037
  ...options.stateDir ? { homeDir: options.stateDir } : {}
2864
3038
  });
2865
3039
  const engagements = new EngagementStore({ workspace: options.workspace ?? process.cwd(), dbPath: options.dbPath, ...options.stateDir ? { stateDir: options.stateDir } : {} });
2866
- const operator = new WebOperatorServices({ isTurnActive: () => gateway.hasActiveTurns() });
3040
+ const operator = new WebOperatorServices({ allowChatGptPlan: !options.auth.enabled, isTurnActive: () => gateway.hasActiveTurns() });
2867
3041
  let workflows;
2868
3042
  let engine;
2869
3043
  let triggers;
2870
3044
  let github;
2871
3045
  const dispose = async () => {
2872
- const actions = [() => engine?.dispose(), () => triggers?.dispose(), () => gateway.closeAll(), () => workflows?.dispose(), () => operator.dispose(), () => github?.dispose()];
3046
+ const actions = [() => remoteProviderRelays.dispose(), () => engine?.dispose(), () => triggers?.dispose(), () => gateway.closeAll(), () => workflows?.dispose(), () => operator.dispose(), () => github?.dispose()];
2873
3047
  let failure;
2874
3048
  for (const action of actions) {
2875
3049
  try {
@@ -2900,8 +3074,10 @@ async function createWorkspaceServices(options) {
2900
3074
  return { executionId: result.execution.id };
2901
3075
  }
2902
3076
  } });
3077
+ const schedules = triggers;
3078
+ gateway.attachScheduleLifecycle({ invoke: (sessionId, name, args, context) => schedules.invokeLifecycle(sessionId, name, args, context) });
2903
3079
  github = new GitHubPublicationAuth();
2904
- return { skills, gateway, engagements, operator, workflows: workflow, engine, triggers, github, dispose };
3080
+ return { remoteProviderRelays, skills, gateway, engagements, operator, workflows: workflow, engine, triggers, github, dispose };
2905
3081
  } catch (cause) {
2906
3082
  await dispose().catch(() => {
2907
3083
  });
@@ -2911,7 +3087,7 @@ async function createWorkspaceServices(options) {
2911
3087
 
2912
3088
  // packages/cli/src/web/team-setup.ts
2913
3089
  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";
3090
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
2915
3091
  import { dirname, join as join4, resolve as resolve3 } from "node:path";
2916
3092
  var setupSchema = external_exports.object({
2917
3093
  workspaceName: external_exports.string().trim().min(1).max(160),
@@ -2977,7 +3153,7 @@ var TeamSetupService = class {
2977
3153
  if (auth.members().length >= 1e3) throw new TeamAuthError("Workspace member limit reached", 409);
2978
3154
  this.busy = true;
2979
3155
  const lockPath = `${this.configPath}.lock`;
2980
- const tempPath = `${this.configPath}.${randomUUID5()}.tmp`;
3156
+ const tempPath = `${this.configPath}.${randomUUID7()}.tmp`;
2981
3157
  let lockFD;
2982
3158
  let temporaryFD;
2983
3159
  try {
@@ -3034,7 +3210,7 @@ var TeamSetupService = class {
3034
3210
  try {
3035
3211
  const data = parsed.data;
3036
3212
  const config = {
3037
- workspace: { id: randomUUID5(), name: data.workspaceName },
3213
+ workspace: { id: randomUUID7(), name: data.workspaceName },
3038
3214
  users: [{ id: data.userId, name: data.displayName, role: "owner", passwordHash: await hashTeamPassword(data.password) }]
3039
3215
  };
3040
3216
  const auth = new TeamAuth({ config, origin: this.options.origin() });
@@ -3087,12 +3263,12 @@ async function handleTeamSetupRequest(req, url, setup, readBody) {
3087
3263
  }
3088
3264
 
3089
3265
  // packages/cli/src/web/collaboration.ts
3090
- import { createHash as createHash7, randomUUID as randomUUID6 } from "node:crypto";
3266
+ import { createHash as createHash7, randomUUID as randomUUID8 } from "node:crypto";
3091
3267
  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
3268
  import { join as join5 } from "node:path";
3093
3269
  var CollaborationError = class extends Error {
3094
- constructor(message2, statusCode = 400) {
3095
- super(message2);
3270
+ constructor(message, statusCode = 400) {
3271
+ super(message);
3096
3272
  this.statusCode = statusCode;
3097
3273
  this.name = "CollaborationError";
3098
3274
  }
@@ -3172,9 +3348,9 @@ var CollaborationService = class {
3172
3348
  });
3173
3349
  }
3174
3350
  /** 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 });
3351
+ notifyChanged(kind, id2) {
3352
+ 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.");
3353
+ this.#publish({ type: "changed", kind, id: id2 });
3178
3354
  }
3179
3355
  #actor(sessionId) {
3180
3356
  if (typeof sessionId !== "string" || !sessionId || sessionId.length > 4096) throw new CollaborationError("An authenticated team session is required.", 401);
@@ -3201,7 +3377,7 @@ var CollaborationService = class {
3201
3377
  if (bytes.length > 32 * 1024 * 1024) throw new Error("size");
3202
3378
  let source = JSON.parse(bytes.toString("utf8"));
3203
3379
  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 })) };
3380
+ 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
3381
  const parsed = stateSchema3.parse(source);
3206
3382
  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
3383
  return parsed.rooms;
@@ -3216,7 +3392,7 @@ var CollaborationService = class {
3216
3392
  if (!state.success) throw new CollaborationError("Collaboration record limit reached.", 409);
3217
3393
  const data = JSON.stringify(state.data);
3218
3394
  if (Buffer.byteLength(data) > 32 * 1024 * 1024) throw new CollaborationError("Collaboration storage limit reached.", 409);
3219
- const temporary = this.#path + "." + randomUUID6() + ".tmp";
3395
+ const temporary = this.#path + "." + randomUUID8() + ".tmp";
3220
3396
  try {
3221
3397
  writeFileSync5(temporary, data, { flag: "wx", mode: 384 });
3222
3398
  renameSync5(temporary, this.#path);
@@ -3278,11 +3454,11 @@ var CollaborationService = class {
3278
3454
  unlinkSync5(this.#lock);
3279
3455
  }
3280
3456
  }
3281
- #roomKey(kind, id) {
3282
- return kind + ":" + id;
3457
+ #roomKey(kind, id2) {
3458
+ return kind + ":" + id2;
3283
3459
  }
3284
- #room(rows, kind, id) {
3285
- const row = rows.find((item) => item.kind === kind && item.id === id);
3460
+ #room(rows, kind, id2) {
3461
+ const row = rows.find((item) => item.kind === kind && item.id === id2);
3286
3462
  if (!row) throw new CollaborationError("Shared resource room not found.", 404);
3287
3463
  return row;
3288
3464
  }
@@ -3304,8 +3480,8 @@ var CollaborationService = class {
3304
3480
  const { workspaceId: _workspace, ...viewer } = actor;
3305
3481
  return viewer;
3306
3482
  }
3307
- #presenceFor(kind, id) {
3308
- const entries = this.#presence.get(this.#roomKey(kind, id));
3483
+ #presenceFor(kind, id2) {
3484
+ const entries = this.#presence.get(this.#roomKey(kind, id2));
3309
3485
  const presence = /* @__PURE__ */ new Map();
3310
3486
  for (const [key, entry] of entries ?? []) {
3311
3487
  if (!entry.viewing || entry.updatedAt + this.#presenceMs <= this.#now()) {
@@ -3332,15 +3508,15 @@ var CollaborationService = class {
3332
3508
  return [...presence.values()];
3333
3509
  }
3334
3510
  /** Routing must validate that the underlying resource exists before registration. */
3335
- registerRoom(kind, id, sessionId) {
3511
+ registerRoom(kind, id2, sessionId) {
3336
3512
  const actor = this.#actor(sessionId);
3337
- const valid = external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id });
3513
+ const valid = external_exports.object({ kind: resourceKindSchema, id: external_exports.string().min(1).max(160) }).safeParse({ kind, id: id2 });
3338
3514
  if (!valid.success) throw new CollaborationError("Invalid resource room.");
3339
3515
  let joined = false;
3340
3516
  this.#transaction((rows) => {
3341
- let row = rows.find((item) => item.kind === kind && item.id === id);
3517
+ let row = rows.find((item) => item.kind === kind && item.id === id2);
3342
3518
  if (!row) {
3343
- row = { kind, id, revision: 0, createdBy: this.#attribution(actor), createdAt: this.#now(), members: [] };
3519
+ row = { kind, id: id2, revision: 0, createdBy: this.#attribution(actor), createdAt: this.#now(), members: [] };
3344
3520
  rows.push(row);
3345
3521
  }
3346
3522
  const revision2 = row.revision;
@@ -3348,7 +3524,7 @@ var CollaborationService = class {
3348
3524
  joined = row.revision !== revision2;
3349
3525
  });
3350
3526
  if (joined) this.#notifyPresence();
3351
- return this.roomSnapshot(kind, id, sessionId);
3527
+ return this.roomSnapshot(kind, id2, sessionId);
3352
3528
  }
3353
3529
  register(conversationId, sessionId) {
3354
3530
  this.registerRoom("conversation", conversationId, sessionId);
@@ -3357,10 +3533,10 @@ var CollaborationService = class {
3357
3533
  hasConversation(conversationId) {
3358
3534
  return this.#read().some((row) => row.kind === "conversation" && row.id === conversationId);
3359
3535
  }
3360
- roomSnapshot(kind, id, sessionId) {
3536
+ roomSnapshot(kind, id2, sessionId) {
3361
3537
  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) };
3538
+ const row = this.#room(this.#read(), kind, id2);
3539
+ 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
3540
  }
3365
3541
  snapshot(conversationId, sessionId) {
3366
3542
  const { kind: _kind, id: _id, ...snapshot } = this.roomSnapshot("conversation", conversationId, sessionId);
@@ -3374,19 +3550,19 @@ var CollaborationService = class {
3374
3550
  });
3375
3551
  return { workspaceId: this.#options.workspaceId, viewer: this.#viewer(actor), rooms };
3376
3552
  }
3377
- heartbeatRoom(kind, id, sessionId, input) {
3553
+ heartbeatRoom(kind, id2, sessionId, input) {
3378
3554
  const actor = this.#actor(sessionId);
3379
3555
  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
3556
  if (!parsed.success) throw new CollaborationError("Invalid presence update.");
3381
- this.#presenceFor(kind, id);
3557
+ this.#presenceFor(kind, id2);
3382
3558
  let joined = false;
3383
3559
  this.#transaction((rows) => {
3384
- const row = this.#room(rows, kind, id);
3560
+ const row = this.#room(rows, kind, id2);
3385
3561
  const revision2 = row.revision;
3386
3562
  this.#join(row, actor);
3387
3563
  joined = row.revision !== revision2;
3388
3564
  });
3389
- const key = this.#roomKey(kind, id);
3565
+ const key = this.#roomKey(kind, id2);
3390
3566
  let entries = this.#presence.get(key);
3391
3567
  if (!entries) {
3392
3568
  entries = /* @__PURE__ */ new Map();
@@ -3398,7 +3574,7 @@ var CollaborationService = class {
3398
3574
  if (parsed.data.viewing) entries.set(entryKey, { ...this.#attribution(actor), sessionId, viewing: true, typing: parsed.data.typing, updatedAt: this.#now() });
3399
3575
  else entries.delete(entryKey);
3400
3576
  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);
3577
+ return this.roomSnapshot(kind, id2, sessionId);
3402
3578
  }
3403
3579
  heartbeat(conversationId, sessionId, input) {
3404
3580
  this.heartbeatRoom("conversation", conversationId, sessionId, input);
@@ -3457,29 +3633,133 @@ async function handleTeamRequest(req, url, auth, collaboration, gateway, readBod
3457
3633
  if (resource2) {
3458
3634
  if (method !== "POST") throw new TeamAuthError("Method not allowed.", 405);
3459
3635
  const kind = resource2[1];
3460
- const id2 = resource2[2];
3461
- if (kind === "conversation") gateway.get(id2);
3462
- else if (validateResource) await validateResource(kind, id2);
3636
+ const id3 = resource2[2];
3637
+ if (kind === "conversation") gateway.get(id3);
3638
+ else if (validateResource) await validateResource(kind, id3);
3463
3639
  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()) };
3640
+ collaboration.registerRoom(kind, id3, session);
3641
+ return { status: 200, data: collaboration.heartbeatRoom(kind, id3, session, await readBody()) };
3466
3642
  }
3467
3643
  const match = /^conversations\/([A-Za-z0-9_.:-]+)(?:\/(presence))?$/.exec(path);
3468
3644
  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()) };
3645
+ const id2 = match[1];
3646
+ gateway.get(id2);
3647
+ if (!collaboration.hasConversation(id2)) collaboration.register(id2, session);
3648
+ if (!match[2] && method === "GET") return { status: 200, data: collaboration.snapshot(id2, session) };
3649
+ if (match[2] === "presence" && method === "POST") return { status: 200, data: collaboration.heartbeat(id2, session, await readBody()) };
3474
3650
  throw new TeamAuthError("Method not allowed.", 405);
3475
3651
  }
3476
- function assertTeamConsoleMutation(req, id, auth, collaboration, gateway) {
3652
+ function assertTeamConsoleMutation(req, id2, auth, collaboration, gateway) {
3477
3653
  if (!auth.enabled) return;
3478
- gateway.get(id);
3654
+ gateway.get(id2);
3479
3655
  const session = getSessionId(req);
3480
3656
  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);
3657
+ if (!collaboration.hasConversation(id2)) collaboration.register(id2, session);
3658
+ collaboration.assertWriter(id2, session);
3659
+ }
3660
+
3661
+ // packages/cli/src/web/browser-live-routes.ts
3662
+ var registry = { list: listBrowserViews, subscribe: subscribeBrowserView };
3663
+ var HEARTBEAT_MS = 15e3;
3664
+ var STREAM_LIFETIME_MS = 5 * 6e4;
3665
+ async function handleBrowserLiveRequest(req, res, url, ownerId, pageId, source = registry) {
3666
+ const reply = (status, body) => {
3667
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
3668
+ res.end(JSON.stringify(body));
3669
+ };
3670
+ if (url.search) {
3671
+ reply(400, { error: "Browser view endpoints do not accept query parameters." });
3672
+ return;
3673
+ }
3674
+ if (req.method !== "GET") {
3675
+ reply(405, { error: "Use GET for browser views." });
3676
+ return;
3677
+ }
3678
+ const views = source.list(ownerId);
3679
+ if (!pageId) {
3680
+ reply(200, { views });
3681
+ return;
3682
+ }
3683
+ const view = views.find((item) => item.pageId === pageId);
3684
+ if (!view) {
3685
+ reply(404, { error: "Browser view was closed or does not belong to this conversation." });
3686
+ return;
3687
+ }
3688
+ if (!view.liveAvailable) {
3689
+ reply(409, { error: "Live browser view is unavailable for this execution backend." });
3690
+ return;
3691
+ }
3692
+ res.writeHead(200, {
3693
+ "Content-Type": "text/event-stream",
3694
+ "Cache-Control": "no-cache, no-store, no-transform",
3695
+ "X-Accel-Buffering": "no",
3696
+ Connection: "keep-alive"
3697
+ });
3698
+ res.flushHeaders?.();
3699
+ let closed = false;
3700
+ let blocked = false;
3701
+ let unsubscribe;
3702
+ let heartbeat;
3703
+ let lifetime;
3704
+ const cleanup = () => {
3705
+ if (closed) return;
3706
+ closed = true;
3707
+ clearInterval(heartbeat);
3708
+ clearTimeout(lifetime);
3709
+ unsubscribe?.();
3710
+ unsubscribe = void 0;
3711
+ res.off("drain", drain);
3712
+ res.off("close", cleanup);
3713
+ res.off("error", cleanup);
3714
+ req.off("aborted", cleanup);
3715
+ };
3716
+ const drain = () => {
3717
+ blocked = false;
3718
+ };
3719
+ const write = (event, data) => {
3720
+ if (closed || blocked || res.destroyed || res.writableEnded) return;
3721
+ blocked = !res.write(`event: ${event}
3722
+ data: ${JSON.stringify(data)}
3723
+
3724
+ `);
3725
+ };
3726
+ const finish = (status) => {
3727
+ write("state", { status });
3728
+ cleanup();
3729
+ if (!res.destroyed && !res.writableEnded) res.end();
3730
+ };
3731
+ res.on("drain", drain);
3732
+ res.on("close", cleanup);
3733
+ res.on("error", cleanup);
3734
+ req.on("aborted", cleanup);
3735
+ write("state", { status: "connecting" });
3736
+ heartbeat = setInterval(() => {
3737
+ try {
3738
+ if (!source.list(ownerId).some((item) => item.pageId === pageId)) {
3739
+ finish("closed");
3740
+ return;
3741
+ }
3742
+ if (!closed && !blocked) blocked = !res.write(": heartbeat\n\n");
3743
+ } catch {
3744
+ finish("unavailable");
3745
+ }
3746
+ }, HEARTBEAT_MS);
3747
+ lifetime = setTimeout(() => finish("expired"), STREAM_LIFETIME_MS);
3748
+ heartbeat.unref?.();
3749
+ lifetime.unref?.();
3750
+ try {
3751
+ const stop = await source.subscribe(ownerId, pageId, (frame) => {
3752
+ if (closed || blocked || frame.mimeType !== "image/jpeg" || frame.data.length > 4 * 1024 * 1024) return;
3753
+ write("frame", frame);
3754
+ });
3755
+ if (closed) stop();
3756
+ else {
3757
+ unsubscribe = stop;
3758
+ write("state", { status: "live" });
3759
+ }
3760
+ } catch {
3761
+ finish("unavailable");
3762
+ }
3483
3763
  }
3484
3764
 
3485
3765
  // packages/cli/src/web/team-presence-stream.ts
@@ -3643,8 +3923,8 @@ function discoverSshHosts(configPath = join6(homedir3(), ".ssh", "config")) {
3643
3923
 
3644
3924
  // packages/cli/src/commands/dashboard.ts
3645
3925
  var WebRequestError = class extends Error {
3646
- constructor(message2, statusCode) {
3647
- super(message2);
3926
+ constructor(message, statusCode) {
3927
+ super(message);
3648
3928
  this.statusCode = statusCode;
3649
3929
  }
3650
3930
  statusCode;
@@ -3701,7 +3981,7 @@ function openBrowser(url) {
3701
3981
  execFile2(cmd, args, () => {
3702
3982
  });
3703
3983
  }
3704
- function json(res, status, body) {
3984
+ function json2(res, status, body) {
3705
3985
  res.writeHead(status, {
3706
3986
  "Content-Type": "application/json; charset=utf-8",
3707
3987
  "Cache-Control": "no-store"
@@ -4364,7 +4644,11 @@ function resolveDashboardAssetDir(explicitAssetDir) {
4364
4644
  }
4365
4645
  const embedded = materializeEmbeddedDashboardAssets(DASHBOARD_ASSETS);
4366
4646
  if (embedded) return embedded;
4647
+ const checkoutPackage = resolve4(moduleDir, "..", "..");
4367
4648
  for (const candidate of [
4649
+ // A source-built CLI can run from a different project directory. Resolve
4650
+ // its sibling app from the checkout, never from an unrelated installed CLI.
4651
+ ...existsSync2(join7(checkoutPackage, "src", "index.ts")) ? [join7(checkoutPackage, "..", "dashboard", "dist")] : [],
4368
4652
  join7(process.cwd(), "packages", "dashboard", "dist"),
4369
4653
  join7(process.cwd(), "dist", "dashboard")
4370
4654
  ]) {
@@ -4387,7 +4671,7 @@ function requireControlToken(req, res, controlToken, engineBearer) {
4387
4671
  const matches = (a, b) => typeof a === "string" && Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual3(Buffer.from(a), Buffer.from(b));
4388
4672
  const bearer = typeof req.headers.authorization === "string" && req.headers.authorization.startsWith("Bearer ") ? req.headers.authorization.slice(7) : void 0;
4389
4673
  if (!matches(provided, controlToken) && !matches(bearer, controlToken) && !(engineBearer && matches(bearer, engineBearer))) {
4390
- json(res, 403, { error: "Invalid or missing control token" });
4674
+ json2(res, 403, { error: "Invalid or missing control token" });
4391
4675
  return false;
4392
4676
  }
4393
4677
  return true;
@@ -4407,59 +4691,59 @@ async function handleWebConsoleApiRequest(req, res, requestUrl, gateway, operato
4407
4691
  const input = method === "GET" || method === "HEAD" ? void 0 : await readJson(req);
4408
4692
  const member = team?.auth.resolveSession(req);
4409
4693
  const author = member ? { userId: member.userId, displayName: member.displayName } : void 0;
4410
- if (team && method !== "GET" && method !== "HEAD") {
4694
+ if (team?.auth.enabled && method !== "GET" && method !== "HEAD") {
4411
4695
  const readResume = /^saved\/[^/]+\/resume$/.test(path) && input !== null && typeof input === "object" && Object.keys(input).length === 0;
4412
4696
  if (/^saved\/[^/]+\/resume$/.test(path) && team.auth.resolveSession(req)?.role !== "owner" && !readResume) throw new WebRequestError("Only workspace owners can override a saved conversation's configuration.", 403);
4413
4697
  const liveId = /^sessions\/([^/]+)/.exec(path)?.[1];
4414
4698
  const bodyId = input && typeof input === "object" && "sessionId" in input && typeof input.sessionId === "string" ? input.sessionId : void 0;
4415
4699
  const savedId = /^saved\/([^/]+)/.exec(path)?.[1];
4416
4700
  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);
4701
+ const id2 = liveId ?? bodyId ?? savedLiveId;
4702
+ if (id2 && !readResume) assertTeamConsoleMutation(req, id2, team.auth, team.collaboration, gateway);
4419
4703
  }
4420
4704
  try {
4421
4705
  if (path === "execution" && method === "GET") {
4422
- json(res, 200, await workbenchStatus());
4706
+ json2(res, 200, await workbenchStatus());
4423
4707
  return true;
4424
4708
  }
4425
4709
  if (path === "github" && method === "GET") {
4426
- json(res, 200, { github: await github.status() });
4710
+ json2(res, 200, { github: await github.status() });
4427
4711
  return true;
4428
4712
  }
4429
4713
  if (path === "github/connect" && method === "POST") {
4430
- json(res, 200, { github: await github.connect(input) });
4714
+ json2(res, 200, { github: await github.connect(input) });
4431
4715
  return true;
4432
4716
  }
4433
4717
  if (path === "github/device-auth" && method === "POST") {
4434
- json(res, 202, { github: await github.start() });
4718
+ json2(res, 202, { github: await github.start() });
4435
4719
  return true;
4436
4720
  }
4437
4721
  if (path === "github/device-auth" && method === "DELETE") {
4438
- json(res, 200, { github: await github.cancel() });
4722
+ json2(res, 200, { github: await github.cancel() });
4439
4723
  return true;
4440
4724
  }
4441
4725
  const trigger = await triggers.handle(method, requestUrl, input);
4442
4726
  if (trigger) {
4443
- json(res, trigger.status, trigger.data);
4727
+ json2(res, trigger.status, trigger.data);
4444
4728
  return true;
4445
4729
  }
4446
4730
  const workflow = await workflows.handle(requestUrl.pathname, method, input, requestUrl.searchParams);
4447
4731
  if (workflow) {
4448
4732
  if (team?.collaboration && method !== "GET" && method !== "HEAD" && workflow.status >= 200 && workflow.status < 300) {
4449
4733
  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));
4734
+ const id3 = definition && typeof definition === "object" && "id" in definition && typeof definition.id === "string" ? definition.id : /^\/api\/console\/workflow-definitions\/([^/]+)/.exec(requestUrl.pathname)?.[1];
4735
+ if (id3) team.collaboration.notifyChanged("workflow", decodeURIComponent(id3));
4452
4736
  }
4453
- json(res, workflow.status, workflow.data);
4737
+ json2(res, workflow.status, workflow.data);
4454
4738
  return true;
4455
4739
  }
4456
4740
  const service = await operator.handle(requestUrl.pathname, method, input, requestUrl.searchParams);
4457
4741
  if (service) {
4458
- json(res, service.status, service.data);
4742
+ json2(res, service.status, service.data);
4459
4743
  return true;
4460
4744
  }
4461
4745
  if (path === "search" && method === "GET") {
4462
- json(res, 200, await gateway.search({
4746
+ json2(res, 200, await gateway.search({
4463
4747
  q: requestUrl.searchParams.get("q") ?? "",
4464
4748
  ...requestUrl.searchParams.has("limit") ? { limit: Number(requestUrl.searchParams.get("limit")) } : {},
4465
4749
  ...requestUrl.searchParams.has("offset") ? { offset: Number(requestUrl.searchParams.get("offset")) } : {}
@@ -4467,84 +4751,99 @@ async function handleWebConsoleApiRequest(req, res, requestUrl, gateway, operato
4467
4751
  return true;
4468
4752
  }
4469
4753
  if (path === "sessions") {
4470
- if (method === "GET") json(res, 200, { sessions: gateway.list() });
4754
+ if (method === "GET") json2(res, 200, { sessions: gateway.list() });
4471
4755
  else if (method === "POST") {
4472
4756
  const session = gateway.create(input);
4473
4757
  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." });
4758
+ json2(res, 201, { session });
4759
+ } else json2(res, 405, { error: "Method not allowed." });
4476
4760
  return true;
4477
4761
  }
4478
4762
  const parts = path.split("/").map((part) => decodeURIComponent(part));
4479
4763
  if (parts.some((part) => !/^[A-Za-z0-9_.:-]+$/.test(part))) throw new ConsoleGatewayError("Invalid resource identifier.", 400);
4480
4764
  if (parts[0] === "saved") {
4481
- const id2 = parts[1];
4482
- if (!id2 && method === "GET") {
4765
+ const id3 = parts[1];
4766
+ if (!id3 && method === "GET") {
4483
4767
  const limit = requestUrl.searchParams.get("limit");
4484
- json(res, 200, { sessions: gateway.listSaved({
4768
+ json2(res, 200, { sessions: gateway.listSaved({
4485
4769
  ...requestUrl.searchParams.get("cwd") ? { cwd: requestUrl.searchParams.get("cwd") } : {},
4486
4770
  ...limit ? { limit: Number(limit) } : {}
4487
4771
  }) });
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." });
4772
+ } else if (id3 && parts.length === 2 && method === "GET") json2(res, 200, gateway.loadSaved(id3));
4773
+ else if (id3 && parts.length === 2 && method === "DELETE") {
4774
+ gateway.deleteSaved(id3);
4775
+ json2(res, 200, { ok: true });
4776
+ } else if (id3 && parts[2] === "archive" && method === "POST") json2(res, 200, { session: gateway.archiveSaved(id3, input) });
4777
+ else if (id3 && parts[2] === "resume" && method === "POST") json2(res, 201, { session: await gateway.resume(id3, input) });
4778
+ else if (id3 && parts[2] === "export" && method === "GET") json2(res, 200, gateway.exportSaved(id3));
4779
+ else json2(res, 405, { error: "Method not allowed." });
4496
4780
  return true;
4497
4781
  }
4498
4782
  if (parts[0] !== "sessions" || !parts[1]) return false;
4499
- const id = parts[1];
4783
+ const id2 = parts[1];
4500
4784
  const action = parts[2];
4501
- if (!action && method === "GET") json(res, 200, { snapshot: gateway.get(id) });
4785
+ if (action === "browser" && parts[3] === "views" && (parts.length === 4 || parts.length === 6 && parts[5] === "stream")) {
4786
+ gateway.get(id2);
4787
+ if (team?.auth.enabled) {
4788
+ const sessionId = getSessionId(req);
4789
+ if (!sessionId || !team.collaboration) throw new WebRequestError("Sign in to this workspace.", 403);
4790
+ if (!team.collaboration.hasConversation(id2)) team.collaboration.register(id2, sessionId);
4791
+ team.collaboration.snapshot(id2, sessionId);
4792
+ }
4793
+ await handleBrowserLiveRequest(req, res, requestUrl, id2, parts[4]);
4794
+ return true;
4795
+ }
4796
+ if (!action && method === "GET") json2(res, 200, { snapshot: gateway.get(id2) });
4502
4797
  else if (!action && method === "DELETE") {
4503
- workflows.cancelSession(id);
4504
- await gateway.close(id);
4505
- json(res, 200, { ok: true });
4798
+ workflows.cancelSession(id2);
4799
+ await gateway.close(id2);
4800
+ json2(res, 200, { ok: true });
4506
4801
  } else if (action === "events" && method === "GET") {
4507
- json(res, 200, gateway.eventsAfter(id, consoleEventsAfter(requestUrl.searchParams.get("after"))));
4802
+ json2(res, 200, gateway.eventsAfter(id2, consoleEventsAfter(requestUrl.searchParams.get("after"))));
4508
4803
  } else if (action === "messages" && method === "POST") {
4509
- 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) });
4804
+ const message = author && input && typeof input === "object" && !("workerId" in input) && (!("mode" in input) || input.mode === "send") ? { ...input, mode: "queue" } : input;
4805
+ const { queuedMessageId, ...session } = await gateway.send(id2, message, author);
4806
+ json2(res, 202, { session, ...queuedMessageId ? { queuedMessageId } : {} });
4807
+ } else if (action === "cancel" && method === "POST") json2(res, 200, { session: await gateway.cancel(id2) });
4512
4808
  else if (action === "connection" && parts[3] === "reload" && parts.length === 4 && method === "POST") {
4513
4809
  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) });
4810
+ json2(res, 200, { session: await gateway.reloadConnection(id2) });
4811
+ } else if (action === "configuration" && method === "PATCH") json2(res, 200, { session: await gateway.configure(id2, input) });
4812
+ else if (action === "clear" && method === "POST") json2(res, 200, { session: await gateway.clear(id2) });
4813
+ else if (action === "continue" && method === "POST") json2(res, 202, { session: await gateway.continue(id2, input, author) });
4814
+ else if (action === "harness" && method === "POST") json2(res, 200, await gateway.harness(id2, input));
4815
+ else if (action === "save" && method === "POST") json2(res, 200, { session: gateway.save(id2) });
4520
4816
  else if (action === "archive" && method === "POST") {
4521
- workflows.cancelSession(id);
4522
- json(res, 200, { session: await gateway.archive(id) });
4817
+ workflows.cancelSession(id2);
4818
+ json2(res, 200, { session: await gateway.archive(id2) });
4523
4819
  } 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) });
4820
+ workflows.cancelSession(id2);
4821
+ json2(res, 200, await gateway.delete(id2));
4822
+ } else if (action === "export" && method === "GET") json2(res, 200, gateway.export(id2));
4823
+ else if (action === "queue" && parts[3] && parts[4] === "send" && parts.length === 5 && method === "POST") {
4824
+ external_exports.object({}).strict().parse(input ?? {});
4825
+ json2(res, 202, { session: gateway.sendQueuedNow(id2, parts[3]) });
4826
+ } else if (action === "queue" && method === "DELETE") json2(res, 200, { session: gateway.removeQueued(id2, parts[3]) });
4827
+ else if (action === "decisions" && parts[3] && method === "POST") json2(res, 200, { session: gateway.resolveDecision(id2, parts[3], input) });
4529
4828
  else if (action === "workers") {
4530
4829
  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) });
4830
+ if (!workerId && method === "GET") json2(res, 200, { workers: gateway.workers(id2) });
4831
+ else if (workerId === "stop" && method === "POST") json2(res, 200, { session: await gateway.drainWorkers(id2) });
4832
+ else if (workerId && parts[4] === "export" && method === "GET") json2(res, 200, gateway.exportWorker(id2, workerId));
4833
+ else if (workerId && parts[4] === "stop" && method === "POST") json2(res, 200, { worker: await gateway.stopWorker(id2, workerId) });
4535
4834
  else if (workerId && parts[4] === "messages" && method === "POST") {
4536
4835
  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." });
4836
+ json2(res, 202, { session: gateway.sendWorker(id2, workerId, body.text, author) });
4837
+ } else json2(res, 405, { error: "Method not allowed." });
4838
+ } else json2(res, 405, { error: "Method not allowed." });
4540
4839
  return true;
4541
4840
  } catch (error2) {
4542
4841
  if (error2 instanceof external_exports.ZodError) {
4543
- json(res, 400, { error: "Invalid console request payload." });
4842
+ json2(res, 400, { error: "Invalid console request payload." });
4544
4843
  return true;
4545
4844
  }
4546
4845
  if (error2 instanceof ConsoleGatewayError) {
4547
- json(res, error2.statusCode, { error: error2.message });
4846
+ json2(res, error2.statusCode, { error: error2.message });
4548
4847
  return true;
4549
4848
  }
4550
4849
  throw error2;
@@ -4572,10 +4871,10 @@ data: ${JSON.stringify(event)}
4572
4871
  `);
4573
4872
  }
4574
4873
  async function handleApiRequest(req, res, pathname, dbPath, controlToken, engineBearer) {
4575
- const { osecDB: osecDB2 } = await import("./dist-RKLQIXDT.js");
4874
+ const { osecDB: osecDB2 } = await import("./dist-QLMPTFT7.js");
4576
4875
  if (isPresentationEventsStreamPath(pathname)) {
4577
4876
  if (req.method !== "GET") {
4578
- json(res, 405, { error: "Method not allowed" });
4877
+ json2(res, 405, { error: "Method not allowed" });
4579
4878
  return true;
4580
4879
  }
4581
4880
  const db = new osecDB2(dbPath);
@@ -4619,27 +4918,27 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4619
4918
  const controlPath = parseControlPath(pathname);
4620
4919
  if (controlPath) {
4621
4920
  if (req.method !== "POST") {
4622
- json(res, 405, { error: "Method not allowed" });
4921
+ json2(res, 405, { error: "Method not allowed" });
4623
4922
  return true;
4624
4923
  }
4625
4924
  if (!requireControlToken(req, res, controlToken, engineBearer)) {
4626
4925
  return true;
4627
4926
  }
4628
4927
  if (controlPath.action === "recover-stale-workers") {
4629
- const { recoverStaleWorkers } = await import("./orchestrate-OBS37FWQ.js");
4928
+ const { recoverStaleWorkers } = await import("./orchestrate-ZF6ZTSIQ.js");
4630
4929
  const body = await readJson(req);
4631
4930
  const recovered = recoverStaleWorkers(
4632
4931
  dbPath,
4633
4932
  typeof body.staleAfterMs === "number" ? body.staleAfterMs : 3e4
4634
4933
  );
4635
- json(res, 200, { ok: true, recovered });
4934
+ json2(res, 200, { ok: true, recovered });
4636
4935
  return true;
4637
4936
  }
4638
4937
  if (controlPath.action === "prune-stopped-workers") {
4639
4938
  const db = new osecDB2(dbPath);
4640
4939
  try {
4641
4940
  const deleted = db.deleteWorkersByStatus("stopped");
4642
- json(res, 200, { ok: true, deleted });
4941
+ json2(res, 200, { ok: true, deleted });
4643
4942
  } finally {
4644
4943
  db.close();
4645
4944
  }
@@ -4653,7 +4952,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4653
4952
  (worker) => worker.status !== "stopped" && Date.now() - Date.parse(worker.heartbeatAt) < 2e4
4654
4953
  );
4655
4954
  if (activeWorkers.length > 0) {
4656
- json(res, 409, { error: "An active daemon is already heartbeating. Stop it before starting another local daemon." });
4955
+ json2(res, 409, { error: "An active daemon is already heartbeating. Stop it before starting another local daemon." });
4657
4956
  return true;
4658
4957
  }
4659
4958
  } finally {
@@ -4664,21 +4963,21 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4664
4963
  label: typeof body.label === "string" && body.label.trim() ? body.label.trim() : "control-plane-1",
4665
4964
  pollIntervalMs: typeof body.pollIntervalMs === "number" ? body.pollIntervalMs : 2e3
4666
4965
  });
4667
- json(res, 200, { ok: true, ...started });
4966
+ json2(res, 200, { ok: true, ...started });
4668
4967
  return true;
4669
4968
  }
4670
4969
  if (controlPath.action === "stop-daemon") {
4671
4970
  const stopped = stopDaemonWorkers(osecDB2, dbPath);
4672
- json(res, 200, { ok: true, stopped });
4971
+ json2(res, 200, { ok: true, stopped });
4673
4972
  return true;
4674
4973
  }
4675
4974
  if (controlPath.action === "reset-database") {
4676
- const { resetOsecDatabase } = await import("./dist-RKLQIXDT.js");
4677
- const { seedVerificationWorkbench } = await import("./db-BXJNWUYJ.js");
4975
+ const { resetOsecDatabase } = await import("./dist-QLMPTFT7.js");
4976
+ const { seedVerificationWorkbench } = await import("./db-FU3OIGQK.js");
4678
4977
  const body = await readJson(req);
4679
4978
  const seed = typeof body.seed === "string" ? body.seed.trim().toLowerCase() : "verification";
4680
4979
  if (!["verification", "empty"].includes(seed)) {
4681
- json(res, 400, { error: `Unsupported seed preset: ${seed}` });
4980
+ json2(res, 400, { error: `Unsupported seed preset: ${seed}` });
4682
4981
  return true;
4683
4982
  }
4684
4983
  const activeWorkerDb = new osecDB2(dbPath);
@@ -4687,7 +4986,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4687
4986
  (worker) => ["idle", "claiming", "running", "sleeping"].includes(worker.status) && Date.now() - Date.parse(worker.heartbeatAt) < 2e4 && (worker.pid ? isLiveLocalPid(worker.pid) : true)
4688
4987
  );
4689
4988
  if (hasActiveWorker) {
4690
- json(res, 409, { error: "Stop active orchestration daemons before resetting the local database." });
4989
+ json2(res, 409, { error: "Stop active orchestration daemons before resetting the local database." });
4691
4990
  return true;
4692
4991
  }
4693
4992
  } finally {
@@ -4697,7 +4996,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4697
4996
  const resetDb = new osecDB2(dbPath);
4698
4997
  try {
4699
4998
  const seeded = seed === "verification" ? seedVerificationWorkbench(resetDb) : { scans: 0, families: 0, workers: 0 };
4700
- json(res, 200, { ok: true, path, seed, ...seeded });
4999
+ json2(res, 200, { ok: true, path, seed, ...seeded });
4701
5000
  } finally {
4702
5001
  resetDb.close();
4703
5002
  }
@@ -4733,7 +5032,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4733
5032
  });
4734
5033
  const casesById = new Map(cases.map((item) => [item.id, item]));
4735
5034
  const workItemsById = new Map(workItems.map((item) => [item.id, item]));
4736
- json(res, 200, {
5035
+ json2(res, 200, {
4737
5036
  scans: scans.map(summarizeScan),
4738
5037
  cases,
4739
5038
  groups,
@@ -4749,7 +5048,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4749
5048
  const db = new osecDB2(dbPath);
4750
5049
  try {
4751
5050
  const scans = db.listScans(100);
4752
- json(res, 200, { scans: scans.map(summarizeScan) });
5051
+ json2(res, 200, { scans: scans.map(summarizeScan) });
4753
5052
  } finally {
4754
5053
  db.close();
4755
5054
  }
@@ -4783,7 +5082,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4783
5082
  presentation
4784
5083
  };
4785
5084
  });
4786
- json(res, 200, { events });
5085
+ json2(res, 200, { events });
4787
5086
  } finally {
4788
5087
  db.close();
4789
5088
  }
@@ -4795,7 +5094,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4795
5094
  try {
4796
5095
  const scan = db.getScan(scanPath.scanId);
4797
5096
  if (!scan) {
4798
- json(res, 404, { error: "Scan not found" });
5097
+ json2(res, 404, { error: "Scan not found" });
4799
5098
  return true;
4800
5099
  }
4801
5100
  if (scanPath.suffix === "events") {
@@ -4803,7 +5102,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4803
5102
  ...event,
4804
5103
  payload: parsePayload(event.payload)
4805
5104
  }));
4806
- json(res, 200, { scan: summarizeScan(scan), events });
5105
+ json2(res, 200, { scan: summarizeScan(scan), events });
4807
5106
  return true;
4808
5107
  }
4809
5108
  if (scanPath.suffix === "findings") {
@@ -4813,7 +5112,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4813
5112
  scanIds: [scanPath.scanId],
4814
5113
  status: "running"
4815
5114
  });
4816
- json(res, 200, {
5115
+ json2(res, 200, {
4817
5116
  scan: summarizeScan(scan),
4818
5117
  findings,
4819
5118
  groups: groupFindings(
@@ -4824,7 +5123,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4824
5123
  });
4825
5124
  return true;
4826
5125
  }
4827
- json(res, 200, { scan: summarizeScan(scan) });
5126
+ json2(res, 200, { scan: summarizeScan(scan) });
4828
5127
  } finally {
4829
5128
  db.close();
4830
5129
  }
@@ -4844,7 +5143,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4844
5143
  normalizeTriageStatus(body.triageStatus),
4845
5144
  typeof body.triageNote === "string" ? body.triageNote : void 0
4846
5145
  );
4847
- json(res, 200, { ok: true });
5146
+ json2(res, 200, { ok: true });
4848
5147
  return true;
4849
5148
  }
4850
5149
  if (req.method === "POST" && familyPath.action === "workflow") {
@@ -4857,12 +5156,12 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4857
5156
  normalizeWorkflowStatus(body.workflowStatus),
4858
5157
  typeof body.workflowAssignee === "string" ? body.workflowAssignee.trim() : null
4859
5158
  );
4860
- json(res, 200, { ok: true });
5159
+ json2(res, 200, { ok: true });
4861
5160
  return true;
4862
5161
  }
4863
5162
  const rows = db.getRelatedFindings(familyPath.fingerprint).map(normalizeFinding);
4864
5163
  if (rows.length === 0) {
4865
- json(res, 404, { error: "Not found" });
5164
+ json2(res, 404, { error: "Not found" });
4866
5165
  return true;
4867
5166
  }
4868
5167
  const verdicts = db.listVerdicts(rows.map((row) => row.id));
@@ -4904,7 +5203,7 @@ async function handleApiRequest(req, res, pathname, dbPath, controlToken, engine
4904
5203
  sessions,
4905
5204
  events: allEvents
4906
5205
  });
4907
- json(res, 200, {
5206
+ json2(res, 200, {
4908
5207
  consoleCommand: buildFindingConsoleCommand(rows[0], dbPath),
4909
5208
  fingerprint: familyPath.fingerprint,
4910
5209
  case: familyCase,
@@ -4972,7 +5271,7 @@ function registerDashboardCommand(program) {
4972
5271
  const timeCapMs = opts.engineTimeCap === void 0 ? 6e5 : Number(opts.engineTimeCap);
4973
5272
  const costCapUsd = opts.engineCostCap === void 0 ? 5 : Number(opts.engineCostCap);
4974
5273
  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();
5274
+ const controlToken = randomUUID9();
4976
5275
  let teamAuth = new TeamAuth({ configPath: teamConfigPath, origin });
4977
5276
  let teamDirectory = teamAuth.enabled ? join7(homeStateDir(), "teams", createHash8("sha256").update(teamAuth.workspaceId).digest("hex")) : void 0;
4978
5277
  if (teamDirectory) {
@@ -4982,9 +5281,51 @@ function registerDashboardCommand(program) {
4982
5281
  }
4983
5282
  let collaboration = teamAuth.enabled ? new CollaborationService({ workspaceId: teamAuth.workspaceId, stateDir: join7(teamDirectory, "collaboration"), resolveSession: (sessionId) => teamAuth.resolveSessionId(sessionId) }) : void 0;
4984
5283
  assertLocalEngineAvailable(opts.dbPath);
4985
- const capabilities = ["sessions", "workflows", "schedules", "approvals", "events", "workspaces", "artifacts", "model-connections", "operator-services", "process-controls", "learning", "workflow-engine"];
5284
+ const capabilities = ["sessions", "workflows", "schedules", "approvals", "events", "workspaces", "artifacts", "model-connections", "provider-relay", "operator-services", "process-controls", "learning", "workflow-engine"];
4986
5285
  let localHandshake = createBackendHandshake(opts.dbPath, capabilities);
4987
5286
  let backends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake });
5287
+ let modelBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake });
5288
+ const modelRegistry = {
5289
+ handshake: (id2) => {
5290
+ modelBackends.reload();
5291
+ return modelBackends.handshake(id2);
5292
+ },
5293
+ request: (...args) => {
5294
+ modelBackends.reload();
5295
+ return modelBackends.request(...args);
5296
+ }
5297
+ };
5298
+ let localProviderSessions = new LocalProviderSessions({ registry: modelRegistry });
5299
+ const hostRepairs = /* @__PURE__ */ new Map();
5300
+ const hostRepairAbort = new AbortController();
5301
+ const repairManagedHost = (backendId, signal) => {
5302
+ const pending = hostRepairs.get(backendId);
5303
+ if (pending) return pending;
5304
+ const compiled = import.meta.url.startsWith("file:///$bunfs/") || import.meta.url.startsWith("/$bunfs/");
5305
+ const args = [...compiled ? [] : [resolveCliEntrypoint()], "worker", "repair", backendId];
5306
+ const task = new Promise((resolveRepair, reject) => {
5307
+ execFile2(process.execPath, args, { timeout: 75e3, maxBuffer: 65536, signal: signal ? AbortSignal.any([signal, hostRepairAbort.signal]) : hostRepairAbort.signal }, (error2, stdout) => {
5308
+ if (error2) {
5309
+ reject(new WebRequestError("This host could not reconnect. Check its SSH connection and managed engine installation.", 409));
5310
+ return;
5311
+ }
5312
+ try {
5313
+ resolveRepair(JSON.parse(stdout));
5314
+ } catch {
5315
+ reject(new WebRequestError("Host reconnect returned an invalid result.", 502));
5316
+ }
5317
+ });
5318
+ }).finally(() => {
5319
+ hostRepairs.delete(backendId);
5320
+ });
5321
+ hostRepairs.set(backendId, task);
5322
+ return task;
5323
+ };
5324
+ let stopHostReconnect;
5325
+ const watchManagedHosts = () => startManagedHostReconnect({ getRegistry: () => backends, repair: repairManagedHost, onRecovered: async (id2) => {
5326
+ backends.reload();
5327
+ await backends.handshake(id2);
5328
+ } });
4988
5329
  let engine;
4989
5330
  let startupGateway;
4990
5331
  let startupOperator;
@@ -4995,8 +5336,9 @@ function registerDashboardCommand(program) {
4995
5336
  try {
4996
5337
  const { path: assetDir, cleanup: cleanupAssetDir } = resolveDashboardAssetDir(opts.assetDir);
4997
5338
  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 });
5339
+ 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
5340
  let services = await buildServices(teamAuth);
5341
+ stopHostReconnect = watchManagedHosts();
5000
5342
  let { skills, gateway: consoleGateway, engagements, operator, workflows, triggers, github } = services;
5001
5343
  engine = services.engine;
5002
5344
  startupGateway = consoleGateway;
@@ -5015,11 +5357,11 @@ function registerDashboardCommand(program) {
5015
5357
  currentAuth: () => teamAuth,
5016
5358
  activate: async (prepared) => {
5017
5359
  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);
5360
+ const nextHandshake = createBackendHandshake(prepared.dbPath, capabilities);
5361
+ const next = await buildServices(prepared.auth, prepared.dbPath, prepared.stateDir, nextHandshake.serverInstanceId);
5019
5362
  let nextBackends;
5020
5363
  let nextUnregister;
5021
5364
  try {
5022
- const nextHandshake = createBackendHandshake(prepared.dbPath, capabilities);
5023
5365
  nextBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake: nextHandshake });
5024
5366
  const nextAccess = new FindingsAccessTokenStore({ workspaceId: prepared.workspaceId, stateDir: join7(prepared.stateDir, "findings-api-access") });
5025
5367
  const nextRead = new FindingsReadApi({ workspaceId: prepared.workspaceId, dbPath: prepared.dbPath });
@@ -5028,6 +5370,9 @@ function registerDashboardCommand(program) {
5028
5370
  if (consoleGateway.hasActiveTurns() || workflows.hasActiveWork()) throw new TeamAuthError("Finish active work before creating a team workspace.", 409);
5029
5371
  await services.dispose();
5030
5372
  for (const response of activeResponses) if (response !== setupResponse) response.end();
5373
+ stopHostReconnect?.();
5374
+ await localProviderSessions.dispose();
5375
+ modelBackends.dispose();
5031
5376
  backends.dispose();
5032
5377
  unregisterLocalEngine?.();
5033
5378
  services = next;
@@ -5037,6 +5382,8 @@ function registerDashboardCommand(program) {
5037
5382
  collaboration = nextCollaboration;
5038
5383
  localHandshake = nextHandshake;
5039
5384
  backends = nextBackends;
5385
+ modelBackends = new BackendConnectionRegistry({ configPath: opts.backendsConfig, localHandshake: nextHandshake });
5386
+ localProviderSessions = new LocalProviderSessions({ registry: modelRegistry });
5040
5387
  unregisterLocalEngine = nextUnregister;
5041
5388
  ({ skills, gateway: consoleGateway, engagements, operator, workflows, triggers, github } = next);
5042
5389
  engine = next.engine;
@@ -5047,6 +5394,7 @@ function registerDashboardCommand(program) {
5047
5394
  startupWorkflows = workflows;
5048
5395
  startupTriggers = triggers;
5049
5396
  startupGitHub = github;
5397
+ stopHostReconnect = watchManagedHosts();
5050
5398
  } catch (cause) {
5051
5399
  nextBackends?.dispose();
5052
5400
  nextUnregister?.();
@@ -5064,12 +5412,12 @@ function registerDashboardCommand(program) {
5064
5412
  try {
5065
5413
  authorizeWebRequest(req, origin);
5066
5414
  } catch (error2) {
5067
- json(res, error2 instanceof WebRequestError ? error2.statusCode : 403, { error: error2 instanceof Error ? error2.message : "Invalid request origin." });
5415
+ json2(res, error2 instanceof WebRequestError ? error2.statusCode : 403, { error: error2 instanceof Error ? error2.message : "Invalid request origin." });
5068
5416
  return;
5069
5417
  }
5070
5418
  let requestUrl = new URL2(req.url ?? "/", origin);
5071
5419
  const requestId = BackendRequestIdSchema.safeParse(req.headers["x-0-request-id"]);
5072
- res.setHeader("X-0-Request-ID", requestId.success ? requestId.data : randomUUID7());
5420
+ res.setHeader("X-0-Request-ID", requestId.success ? requestId.data : randomUUID9());
5073
5421
  try {
5074
5422
  if (requestUrl.pathname.startsWith("/api/")) {
5075
5423
  const findingsCredential = findingsAccess.authenticate(req, requestUrl);
@@ -5078,7 +5426,7 @@ function registerDashboardCommand(program) {
5078
5426
  if (!findingsCredential) throw new FindingsAccessError("A findings API key is required in the Authorization header.", 401);
5079
5427
  const reply = handleFindingsReadRequest(req, requestUrl, findingsRead);
5080
5428
  if (!reply) throw new FindingsAccessError("Findings API route was not found.", 404);
5081
- json(res, reply.status, reply.data);
5429
+ json2(res, reply.status, reply.data);
5082
5430
  return;
5083
5431
  }
5084
5432
  const ssoNavigation = requestUrl.pathname === "/api/team/auth/sso" || requestUrl.pathname === "/api/team/auth/callback";
@@ -5089,7 +5437,7 @@ function registerDashboardCommand(program) {
5089
5437
  const reply = await handleTeamSetupRequest(req, requestUrl, setup, () => readJson(req));
5090
5438
  if (reply) {
5091
5439
  for (const [name, value] of Object.entries(reply.headers ?? {})) res.setHeader(name, value);
5092
- json(res, reply.status, reply.data);
5440
+ json2(res, reply.status, reply.data);
5093
5441
  return;
5094
5442
  }
5095
5443
  } finally {
@@ -5098,12 +5446,12 @@ function registerDashboardCommand(program) {
5098
5446
  }
5099
5447
  if (setup.status().pending && requestUrl.pathname !== "/api/team/session") throw new WebRequestError("Workspace setup is in progress. Try again shortly.", 503);
5100
5448
  if (handleTeamPresenceStream(req, res, requestUrl, teamAuth, collaboration)) return;
5101
- const teamReply = await handleTeamRequest(req, requestUrl, teamAuth, collaboration, consoleGateway, () => readJson(req), async (kind, id) => {
5449
+ const teamReply = await handleTeamRequest(req, requestUrl, teamAuth, collaboration, consoleGateway, () => readJson(req), async (kind, id2) => {
5102
5450
  if (kind === "report") {
5103
- engagements.get(id);
5451
+ engagements.get(id2);
5104
5452
  return;
5105
5453
  }
5106
- const definition = await workflows.handle(`/api/console/workflow-definitions/${encodeURIComponent(id)}`, "GET", void 0, new URLSearchParams());
5454
+ const definition = await workflows.handle(`/api/console/workflow-definitions/${encodeURIComponent(id2)}`, "GET", void 0, new URLSearchParams());
5107
5455
  if (!definition || definition.status !== 200) throw new WebRequestError("Workflow was not found.", 404);
5108
5456
  });
5109
5457
  if (teamReply) {
@@ -5111,7 +5459,7 @@ function registerDashboardCommand(program) {
5111
5459
  if (teamReply.redirect) {
5112
5460
  res.writeHead(teamReply.status, { Location: teamReply.redirect, "Cache-Control": "no-store" });
5113
5461
  res.end();
5114
- } else json(res, teamReply.status, teamReply.data);
5462
+ } else json2(res, teamReply.status, teamReply.data);
5115
5463
  return;
5116
5464
  }
5117
5465
  const bearer = req.headers.authorization;
@@ -5122,11 +5470,41 @@ function registerDashboardCommand(program) {
5122
5470
  if (expectedEngineId !== void 0 && expectedEngineId !== localHandshake.engineId) throw new WebRequestError("Request belongs to a different engine identity.", 409);
5123
5471
  const findingsAccessReply = await handleFindingsAccessTokenRequest(req, requestUrl, teamAuth, findingsAccess, () => readJson(req));
5124
5472
  if (findingsAccessReply) {
5125
- json(res, findingsAccessReply.status, findingsAccessReply.data);
5473
+ json2(res, findingsAccessReply.status, findingsAccessReply.data);
5474
+ return;
5475
+ }
5476
+ const remoteSessionRoute = /^\/api\/console\/remote-sessions(?:\/([^/]+)\/(configuration|status))?$/.exec(requestUrl.pathname);
5477
+ if (remoteSessionRoute) {
5478
+ const ownerId = engineRequest ? "engine" : teamAuth.resolveSession(req)?.userId ?? "local";
5479
+ try {
5480
+ if (remoteSessionRoute[1] && remoteSessionRoute[2] === "status" && req.method === "GET") {
5481
+ const backendId = BackendDescriptorSchema.shape.id.parse(requestUrl.searchParams.get("backendId"));
5482
+ json2(res, 200, await localProviderSessions.status(backendId, decodeURIComponent(remoteSessionRoute[1]), ownerId));
5483
+ } else if (!remoteSessionRoute[1] && req.method === "POST") {
5484
+ const body = external_exports.object({ backendId: external_exports.string().min(1).max(128), configuration: external_exports.unknown() }).strict().parse(await readJson(req));
5485
+ json2(res, 201, await localProviderSessions.create(body.backendId, body.configuration, ownerId));
5486
+ } else if (remoteSessionRoute[1] && remoteSessionRoute[2] === "configuration" && req.method === "PATCH") {
5487
+ const body = external_exports.object({ backendId: external_exports.string().min(1).max(128), runtime: external_exports.unknown() }).strict().parse(await readJson(req));
5488
+ json2(res, 200, await localProviderSessions.configure(body.backendId, decodeURIComponent(remoteSessionRoute[1]), body.runtime, ownerId));
5489
+ } else throw new WebRequestError("Method not allowed.", 405);
5490
+ } catch (error2) {
5491
+ if (error2 instanceof LocalProviderSessionError) {
5492
+ json2(res, error2.statusCode, { error: error2.message });
5493
+ return;
5494
+ }
5495
+ throw error2;
5496
+ }
5497
+ return;
5498
+ }
5499
+ if (requestUrl.pathname === "/api/console/provider-relays" || requestUrl.pathname.startsWith("/api/console/provider-relays/")) {
5500
+ if (!engineRequest) throw new WebRequestError("Provider relay transport requires an authenticated engine connection.", 403);
5501
+ if (await handleRemoteProviderRelayRequest(req, res, requestUrl, services.remoteProviderRelays, () => readJson(req))) return;
5502
+ }
5503
+ const backendRoute = await handleBackendConnectionRequest(req, res, requestUrl, backends, (backendId, sessionId) => localProviderSessions.release(backendId, sessionId, engineRequest ? "engine" : teamAuth.resolveSession(req)?.userId ?? "local"));
5504
+ if (backendRoute.handled) {
5505
+ modelBackends.reload();
5126
5506
  return;
5127
5507
  }
5128
- const backendRoute = await handleBackendConnectionRequest(req, res, requestUrl, backends);
5129
- if (backendRoute.handled) return;
5130
5508
  if (backendRoute.localPath) requestUrl = new URL2(backendRoute.localPath, origin);
5131
5509
  const skillsRoute = requestUrl.pathname.match(/^\/api\/skills(?:\/(.*))?$/);
5132
5510
  if (skillsRoute) {
@@ -5140,21 +5518,21 @@ function registerDashboardCommand(program) {
5140
5518
  throw new WebRequestError("Invalid skill identifier.", 400);
5141
5519
  }
5142
5520
  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)) });
5521
+ if (!path && method === "GET") json2(res, 200, skills.list(access));
5522
+ else if (!path && method === "POST") json2(res, 201, { skill: skills.create(await readJson(req)) });
5523
+ else if (path === "import" && method === "POST") json2(res, 201, { skill: skills.import(await readJson(req, 12 * 1024 * 1024)) });
5524
+ else if (path === "mounts" && method === "POST") json2(res, 201, { mount: skills.mount(await readJson(req)) });
5147
5525
  else if (path.startsWith("mounts/") && method === "DELETE") {
5148
5526
  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) });
5527
+ json2(res, 200, { ok: true });
5528
+ } else if (path.endsWith("/copy") && method === "POST") json2(res, 201, { skill: skills.copy(decodedPath.slice(0, -5), await readJson(req), access) });
5151
5529
  else if (path.endsWith("/export") && method === "GET") {
5152
- const id = decodedPath.slice(0, -7);
5153
- const bundle = skills.export(id);
5530
+ const id2 = decodedPath.slice(0, -7);
5531
+ const bundle = skills.export(id2);
5154
5532
  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) });
5533
+ json2(res, 200, bundle);
5534
+ } else if (path && method === "GET") json2(res, 200, { skill: skills.get(decodedPath, access) });
5535
+ else if (path && method === "PUT") json2(res, 200, { skill: skills.update(decodedPath, await readJson(req), access) });
5158
5536
  else throw new WebRequestError("Method not allowed.", 405);
5159
5537
  return;
5160
5538
  }
@@ -5162,34 +5540,34 @@ function registerDashboardCommand(program) {
5162
5540
  if (engagementRoute) {
5163
5541
  const member = teamAuth.resolveSession(req);
5164
5542
  const engagementActor = member ? { userId: member.userId, displayName: member.displayName } : void 0;
5165
- const id = engagementRoute[1] ? decodeURIComponent(engagementRoute[1]) : void 0;
5543
+ const id2 = engagementRoute[1] ? decodeURIComponent(engagementRoute[1]) : void 0;
5166
5544
  if (engagementRoute[2]) {
5167
5545
  if (req.method !== "GET") throw new WebRequestError("Use GET to read an engagement report.", 405);
5168
5546
  const format = requestUrl.searchParams.get("format") ?? "json";
5169
5547
  if (format !== "json" && format !== "markdown") throw new WebRequestError("Use json or markdown for an engagement report.", 400);
5170
- const report = engagements.report(id);
5548
+ const report = engagements.report(id2);
5171
5549
  if (format === "json") {
5172
- res.setHeader("Content-Disposition", `attachment; filename="engagement-${id}.json"`);
5173
- json(res, 200, report);
5550
+ res.setHeader("Content-Disposition", `attachment; filename="engagement-${id2}.json"`);
5551
+ json2(res, 200, report);
5174
5552
  } 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" });
5553
+ 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
5554
  res.end(engagementMarkdown(report));
5177
5555
  }
5178
- } else if (req.method === "GET") json(res, 200, id ? { engagement: engagements.get(id) } : { engagements: engagements.list() });
5179
- else if (req.method === "POST" && !id) {
5556
+ } else if (req.method === "GET") json2(res, 200, id2 ? { engagement: engagements.get(id2) } : { engagements: engagements.list() });
5557
+ else if (req.method === "POST" && !id2) {
5180
5558
  const engagement = engagements.create(await readJson(req), engagementActor);
5181
5559
  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);
5560
+ json2(res, 201, { engagement });
5561
+ } else if (req.method === "PATCH" && id2) {
5562
+ const engagement = engagements.update(id2, await readJson(req), engagementActor);
5185
5563
  collaboration?.notifyChanged("report", engagement.id);
5186
- json(res, 200, { engagement });
5564
+ json2(res, 200, { engagement });
5187
5565
  } else throw new WebRequestError("Method not allowed.", 405);
5188
5566
  return;
5189
5567
  }
5190
5568
  if (requestUrl.pathname === "/api/workers/hosts") {
5191
5569
  if (req.method !== "GET") throw new WebRequestError("Method not allowed.", 405);
5192
- json(res, 200, { hosts: discoverSshHosts() });
5570
+ json2(res, 200, { hosts: discoverSshHosts() });
5193
5571
  return;
5194
5572
  }
5195
5573
  if (requestUrl.pathname === "/api/workers/managed") {
@@ -5202,7 +5580,15 @@ function registerDashboardCommand(program) {
5202
5580
  return [];
5203
5581
  }
5204
5582
  });
5205
- json(res, 200, { workers });
5583
+ json2(res, 200, { workers });
5584
+ return;
5585
+ }
5586
+ if (requestUrl.pathname === "/api/control/repair-worker") {
5587
+ if (req.method !== "POST") throw new WebRequestError("Use POST to reconnect a host.", 405);
5588
+ const body = external_exports.object({ backendId: BackendDescriptorSchema.shape.id }).strict().parse(await readJson(req));
5589
+ const repaired = await repairManagedHost(body.backendId);
5590
+ backends.reload();
5591
+ json2(res, 200, repaired);
5206
5592
  return;
5207
5593
  }
5208
5594
  const workerStop = requestUrl.pathname.match(/^\/api\/workers\/([^/]+)\/stop$/);
@@ -5224,7 +5610,7 @@ function registerDashboardCommand(program) {
5224
5610
  }
5225
5611
  });
5226
5612
  });
5227
- json(res, 200, result);
5613
+ json2(res, 200, result);
5228
5614
  return;
5229
5615
  }
5230
5616
  if (requestUrl.pathname === "/api/workers/deploy") {
@@ -5257,7 +5643,7 @@ function registerDashboardCommand(program) {
5257
5643
  });
5258
5644
  });
5259
5645
  backends.reload();
5260
- json(res, 202, result);
5646
+ json2(res, 202, result);
5261
5647
  return;
5262
5648
  }
5263
5649
  if (requestUrl.pathname === "/api/control/launch-run") {
@@ -5267,15 +5653,15 @@ function registerDashboardCommand(program) {
5267
5653
  if (body.ensureDaemon) throw new WebRequestError("Engine runs do not require a separate orchestration daemon. Configure a workflow schedule for recurring work.", 400);
5268
5654
  const session = await engine.invoke("create_session", { config: { target: body.target, title: `Assessment: ${body.target}` } });
5269
5655
  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 });
5656
+ json2(res, 202, { ok: true, engineOwned: true, sessionId: session.id, runId: execution.id, jobId: execution.jobId });
5271
5657
  return;
5272
5658
  }
5273
5659
  if (requestUrl.pathname === "/api/workflow-engine/call") {
5274
5660
  if (!engine) throw new WebRequestError("This engine does not admit persistent workflow calls.", 403);
5275
5661
  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));
5662
+ 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
5663
  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));
5664
+ json2(res, 200, await engine.invoke(input.name, input.args));
5279
5665
  return;
5280
5666
  }
5281
5667
  const resumeScan = requestUrl.pathname.match(/^\/api\/scans\/([^/]+)\/resume$/);
@@ -5283,14 +5669,14 @@ function registerDashboardCommand(program) {
5283
5669
  if (req.method !== "POST") throw new WebRequestError("Use POST to resume a scan.", 405);
5284
5670
  const request = ScanResumeRequestSchema.parse(await readJson(req));
5285
5671
  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);
5672
+ json2(res, result.status, result.data);
5287
5673
  return;
5288
5674
  }
5289
5675
  if (/^\/api\/findings\/[^/]+\/impact-assessment$/.test(requestUrl.pathname)) {
5290
5676
  const input = req.method === "POST" || req.method === "DELETE" ? await readJson(req) : void 0;
5291
5677
  const result = handleFindingImpactRequest(requestUrl.pathname, req.method ?? "GET", input, { dbPath: opts.dbPath });
5292
5678
  if (result) {
5293
- json(res, result.status, result.data);
5679
+ json2(res, result.status, result.data);
5294
5680
  return;
5295
5681
  }
5296
5682
  }
@@ -5309,7 +5695,7 @@ function registerDashboardCommand(program) {
5309
5695
  const consoleHandled = await handleWebConsoleApiRequest(req, res, requestUrl, consoleGateway, operator, workflows, triggers, github, engineRequest ? void 0 : { auth: teamAuth, collaboration });
5310
5696
  if (consoleHandled) return;
5311
5697
  const handled = await handleApiRequest(req, res, requestUrl.pathname, opts.dbPath, controlToken, engineBearer);
5312
- if (!handled) json(res, 404, { error: "Not found" });
5698
+ if (!handled) json2(res, 404, { error: "Not found" });
5313
5699
  return;
5314
5700
  }
5315
5701
  if (devUrl) {
@@ -5330,12 +5716,12 @@ function registerDashboardCommand(program) {
5330
5716
  return;
5331
5717
  }
5332
5718
  if (extname(requestUrl.pathname)) {
5333
- json(res, 404, { error: "Asset not found" });
5719
+ json2(res, 404, { error: "Asset not found" });
5334
5720
  return;
5335
5721
  }
5336
5722
  sendFile(res, join7(assetDir, "index.html"), controlToken);
5337
5723
  } catch (err) {
5338
- json(res, errorStatusCode(err), { error: err instanceof Error ? err.message : "Web application request failed." });
5724
+ json2(res, errorStatusCode(err), { error: err instanceof Error ? err.message : "Web application request failed." });
5339
5725
  }
5340
5726
  });
5341
5727
  let dashboardAssetsCleaned = false;
@@ -5383,19 +5769,31 @@ function registerDashboardCommand(program) {
5383
5769
  const shutdown = () => {
5384
5770
  if (shuttingDown) return;
5385
5771
  shuttingDown = true;
5386
- backends.dispose();
5387
- void Promise.resolve(engine?.dispose()).then(() => triggers.dispose()).then(() => consoleGateway.closeAll()).then(() => workflows.dispose()).finally(() => {
5772
+ stopHostReconnect?.();
5773
+ hostRepairAbort.abort();
5774
+ services.remoteProviderRelays.dispose();
5775
+ void localProviderSessions.dispose().then(() => {
5776
+ modelBackends.dispose();
5777
+ backends.dispose();
5778
+ return engine?.dispose();
5779
+ }).then(() => triggers.dispose()).then(() => consoleGateway.closeAll()).then(() => workflows.dispose()).finally(() => {
5388
5780
  operator.dispose();
5389
5781
  github.dispose();
5782
+ for (const response of activeResponses) response.end();
5390
5783
  server.close(() => {
5391
5784
  cleanupDashboardAssets();
5392
5785
  process.exit(0);
5393
5786
  });
5787
+ server.closeAllConnections();
5394
5788
  });
5395
5789
  };
5396
5790
  process.once("SIGINT", shutdown);
5397
5791
  process.once("SIGTERM", shutdown);
5398
5792
  } catch (error2) {
5793
+ stopHostReconnect?.();
5794
+ hostRepairAbort.abort();
5795
+ await localProviderSessions.dispose();
5796
+ modelBackends.dispose();
5399
5797
  backends.dispose();
5400
5798
  await startupTriggers?.dispose().catch(() => {
5401
5799
  });