sliccy 4.28.3 → 4.29.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 (235) hide show
  1. package/README.md +17 -16
  2. package/dist/node-server/bridge-security.d.ts +26 -0
  3. package/dist/node-server/bridge-security.js +41 -2
  4. package/dist/node-server/chrome-launch.d.ts +62 -1
  5. package/dist/node-server/chrome-launch.js +168 -2
  6. package/dist/node-server/electron-controller.d.ts +115 -5
  7. package/dist/node-server/electron-controller.js +167 -28
  8. package/dist/node-server/electron-main.js +37 -2
  9. package/dist/node-server/electron-runtime.d.ts +0 -2
  10. package/dist/node-server/electron-runtime.js +63 -68
  11. package/dist/node-server/fetch-proxy-headers.d.ts +25 -0
  12. package/dist/node-server/fetch-proxy-headers.js +36 -0
  13. package/dist/node-server/index.js +46 -11
  14. package/dist/node-server/routes/fetch-proxy.js +12 -12
  15. package/dist/ui/assets/account-store-lwRLg8qZ.js +38 -0
  16. package/dist/ui/assets/account-store-o8Dx0iny.js +99 -0
  17. package/dist/ui/assets/adobe-oauth-state-DJ7nvXJJ.js +1 -0
  18. package/dist/ui/assets/adobe-oauth-state-fjk_YXgL.js +1 -0
  19. package/dist/ui/assets/adobe-t0y7yh0d.js +2 -0
  20. package/dist/ui/assets/adobe-w6Q7iSKn.js +1 -0
  21. package/dist/ui/assets/{agent-message-to-chat-Bx_Ero1x.js → agent-message-to-chat-yezUzugT.js} +1 -1
  22. package/dist/ui/assets/{anthropic-BaI_sJYa.js → anthropic-DwYAPtk5.js} +1 -1
  23. package/dist/ui/assets/{apps-CywJTObv.js → apps-D-6_Des4.js} +1 -1
  24. package/dist/ui/assets/{azure-openai-B1Zfi2XG.js → azure-openai-BUQWXp4U.js} +1 -1
  25. package/dist/ui/assets/{azure-openai-VRDt5QVP.js → azure-openai-Cza179on.js} +1 -1
  26. package/dist/ui/assets/{azure-openai-responses-CMjHELHX.js → azure-openai-responses-Bnxym5u-.js} +1 -1
  27. package/dist/ui/assets/backend-local-mrUgrni5.js +9 -0
  28. package/dist/ui/assets/{bedrock-camp-DS1dgU6g.js → bedrock-camp-C1lmRgcD.js} +2 -2
  29. package/dist/ui/assets/bsh-watchdog-aV3rA0w_.js +60 -0
  30. package/dist/ui/assets/cdp-bridge-CUqQUcaF.js +1 -0
  31. package/dist/ui/assets/{cdp-D4JkCTey.js → cdp-z9uzg44m.js} +5 -5
  32. package/dist/ui/assets/composer-speech-BJF-32Qm.js +1 -0
  33. package/dist/ui/assets/{connect-surface-C-4WfE1D.js → connect-surface-v46dKJYB.js} +1 -1
  34. package/dist/ui/assets/dip-Iosy_HJh.js +1 -0
  35. package/dist/ui/assets/{dist-DLhwzhGb.js → dist-BHeXHQU_.js} +1 -1
  36. package/dist/ui/assets/{dist-Dn-OG2Uq.js → dist-Fdgn9mju.js} +2 -2
  37. package/dist/ui/assets/ensure-speech-assets-CILvdvdL.js +1 -0
  38. package/dist/ui/assets/{es-DaEzN-e4.js → es-Z-wNZ0C3.js} +4 -4
  39. package/dist/ui/assets/esp8266-CL1TORn3.js +1 -0
  40. package/dist/ui/assets/{esptool-operations-SV6FPHJC.js → esptool-operations-CYdcX9Mk.js} +2 -2
  41. package/dist/ui/assets/extension-bridge-transport-DYOF2OYC.js +1 -0
  42. package/dist/ui/assets/fetch-body-D2PwksUW.js +1 -0
  43. package/dist/ui/assets/fetch-body-DDu_AU5I.js +1 -0
  44. package/dist/ui/assets/{fs-B40WJSMO.js → fs-CruOlxoS.js} +2 -2
  45. package/dist/ui/assets/{fs-FoD_qa2m.js → fs-crZm8xnS.js} +1 -1
  46. package/dist/ui/assets/github-CQtjJB1r.js +2 -0
  47. package/dist/ui/assets/github-FUziE_96.js +1 -0
  48. package/dist/ui/assets/{github-copilot-R5lpJGvp.js → github-copilot-DOj1kxaL.js} +1 -1
  49. package/dist/ui/assets/{github-copilot-BSb0sjqN.js → github-copilot-yTNPvKEy.js} +1 -1
  50. package/dist/ui/assets/{google-yB8wbiw4.js → google-CSaY-DQE.js} +1 -1
  51. package/dist/ui/assets/{google-shared-CeGHiQ17.js → google-shared-RiqqHuiz.js} +1 -1
  52. package/dist/ui/assets/{google-vertex-Dbruz2uo.js → google-vertex-CBHKHWKe.js} +1 -1
  53. package/dist/ui/assets/hear-COc8-jz9.js +1 -0
  54. package/dist/ui/assets/hear-Ca-dJ63I.js +1 -0
  55. package/dist/ui/assets/hid-picker-DUJBI46p.js +1 -0
  56. package/dist/ui/assets/{intercepted-oauth-tLpKagkf.js → intercepted-oauth-E9BfBX02.js} +1 -1
  57. package/dist/ui/assets/js-realm-worker-BIJifCVd.js +47 -0
  58. package/dist/ui/assets/{json-parse-BIGBjpLL.js → json-parse-CWht3lea.js} +1 -1
  59. package/dist/ui/assets/kernel-worker-CFGgP28u.js +10060 -0
  60. package/dist/ui/assets/{kokoro-IuvgZkZk.js → kokoro-B5jJ2WUJ.js} +2 -2
  61. package/dist/ui/assets/{kokoro-pZaPWf_-.js → kokoro-BdpwC7DT.js} +2 -2
  62. package/dist/ui/assets/kokoro-engine-io4cnyoV.js +1 -0
  63. package/dist/ui/assets/{lick-ws-bridge-CO4sVUnO.js → lick-ws-bridge-s5Y2i_Ua.js} +1 -1
  64. package/dist/ui/assets/{local-llm-BA1NDIU9.js → local-llm-CKo8wJkz.js} +1 -1
  65. package/dist/ui/assets/magick-wasm-oRIuPV7a.js +43 -0
  66. package/dist/ui/assets/main-CY_go8Ex.js +24 -0
  67. package/dist/ui/assets/{main-cherry-B1mI8ArD.js → main-cherry-BRV9Q0tu.js} +1 -1
  68. package/dist/ui/assets/{mistral-CJVvXrOL.js → mistral-BTNTllXI.js} +1 -1
  69. package/dist/ui/assets/mount-DlwSk_tU.js +1 -0
  70. package/dist/ui/assets/mount-bridge-client-DDnAgZEL.js +2 -0
  71. package/dist/ui/assets/mount-id-_7_PCY9F.js +1 -0
  72. package/dist/ui/assets/mount-pc-SiJqJ.js +2 -0
  73. package/dist/ui/assets/mount-picker-popup-Hrvzr69c.js +1 -0
  74. package/dist/ui/assets/{mount-table-store-CQJEV6Ij.js → mount-table-store-ggb440m4.js} +1 -1
  75. package/dist/ui/assets/new-session-CfjGoE_n.js +2 -0
  76. package/dist/ui/assets/oauth-bootstrap-Le_gIjYT.js +2 -0
  77. package/dist/ui/assets/oauth-service-D4iuqFv6.js +1 -0
  78. package/dist/ui/assets/oauth-service-YGl8VFS-.js +1 -0
  79. package/dist/ui/assets/{openai-codex-MZbc8sm2.js → openai-codex-CU65yKca.js} +1 -1
  80. package/dist/ui/assets/{openai-codex-DyAASUvZ.js → openai-codex-Dx5taSaF.js} +1 -1
  81. package/dist/ui/assets/{openai-codex-responses-CLrAtex4.js → openai-codex-responses-7JN1dUxK.js} +1 -1
  82. package/dist/ui/assets/{openai-completions-CyC3I8Ls.js → openai-completions-YNL_qr9N.js} +1 -1
  83. package/dist/ui/assets/{openai-responses-D3BggRn6.js → openai-responses-nczmKECx.js} +1 -1
  84. package/dist/ui/assets/{openai-responses-shared-GJLWYx-P.js → openai-responses-shared-9YVaeX5D.js} +1 -1
  85. package/dist/ui/assets/panel-responder-BKgfV-nl.js +1 -0
  86. package/dist/ui/assets/{panel-rpc-Ck8CVyMh.js → panel-rpc-D0J5pskz.js} +1 -1
  87. package/dist/ui/assets/panel-rpc-handlers-BFGsncpn.js +2 -0
  88. package/dist/ui/assets/{picker-approval-tzbkMw-e.js → picker-approval-Dc390nC7.js} +1 -1
  89. package/dist/ui/assets/picker-popup-CpulHQsi.js +1 -0
  90. package/dist/ui/assets/preview-vfs-responder-CSDC7Oy2.js +1 -0
  91. package/dist/ui/assets/provider-Lb9gUmQt.js +2 -0
  92. package/dist/ui/assets/provider-store-access-COfmBqcb.js +1 -0
  93. package/dist/ui/assets/provider-store-access-CjPYCH1J.js +1 -0
  94. package/dist/ui/assets/{provider-DNnOLtMP.js → provider-vMORCKxQ.js} +1 -1
  95. package/dist/ui/assets/{providers-DerbEWIP.js → providers-CkeJGhLV.js} +1 -1
  96. package/dist/ui/assets/proxied-fetch-CKDBmEMl.js +2 -0
  97. package/dist/ui/assets/proxied-fetch-CM0jk_Mr.js +1 -0
  98. package/dist/ui/assets/py-realm-worker-hOFi8vjM.js +242 -0
  99. package/dist/ui/assets/{pyodide-BvA4c-59.js → pyodide-CqQhZelL.js} +2 -2
  100. package/dist/ui/assets/{quick-llm-BOgo_uty.js → quick-llm-CksvHcNV.js} +1 -1
  101. package/dist/ui/assets/{remote-cache-BffIqHgi.js → remote-cache-BL6NRenp.js} +1 -1
  102. package/dist/ui/assets/remote-terminal-view-Qv9yhXQm.js +5 -0
  103. package/dist/ui/assets/remote-vfs-client-Z2qM01yP.js +1 -0
  104. package/dist/ui/assets/secret-env-DncfFSea.js +1 -0
  105. package/dist/ui/assets/secret-topology-bwXIzzAK.js +1 -0
  106. package/dist/ui/assets/secrets-bridge-client-BSipj-5f.js +2 -0
  107. package/dist/ui/assets/serial-picker-B6s4NeaB.js +1 -0
  108. package/dist/ui/assets/session-freezer-1tkfzApv.js +1 -0
  109. package/dist/ui/assets/shared-Bn9ltvMZ.js +1 -0
  110. package/dist/ui/assets/speak-Bri-_DZL.js +1 -0
  111. package/dist/ui/assets/speak-D9WnbzxD.js +2 -0
  112. package/dist/ui/assets/speech-assets-bridge-B8NqCAE8.js +1 -0
  113. package/dist/ui/assets/speech-assets-bridge-BUDIvdid.js +1 -0
  114. package/dist/ui/assets/{sprinkle-bridge-DrAUCEC_.js → sprinkle-bridge-Du2dCM8G.js} +1 -1
  115. package/dist/ui/assets/{sprinkle-manager-BXB1EDB8.js → sprinkle-manager-BhhaIwAz.js} +1 -1
  116. package/dist/ui/assets/{sql-wasm-Cu5Uk_6k.js → sql-wasm-BWbkWL23.js} +1 -1
  117. package/dist/ui/assets/{store-p07O-6qm.js → store-iS53cbT5.js} +1 -1
  118. package/dist/ui/assets/sudo-DH2Ru5S3.js +5 -0
  119. package/dist/ui/assets/tool-ui-DfXu6quJ.js +1 -0
  120. package/dist/ui/assets/tool-ui-t6ocn6AG.js +1 -0
  121. package/dist/ui/assets/transformers-env-2Uj9Fgd2.js +1 -0
  122. package/dist/ui/assets/transformers-env-CgaV7qpU.js +1 -0
  123. package/dist/ui/assets/transformers.web-BxHOGVSB.js +45 -0
  124. package/dist/ui/assets/transformers.web-CuUo4MLm.js +39 -0
  125. package/dist/ui/assets/{tray-leave-runtime-BIU4slWr.js → tray-leave-runtime-ND08HLeb.js} +1 -1
  126. package/dist/ui/assets/{upgrade-detection-CjNy2QVK.js → upgrade-detection-Y3TdGYus.js} +1 -1
  127. package/dist/ui/assets/usb-picker-D_K0weAY.js +1 -0
  128. package/dist/ui/assets/{voice-reply-UcYXGV0I.js → voice-reply-DfKSbOma.js} +1 -1
  129. package/dist/ui/assets/wc-attach-BvytbCpS.js +13 -0
  130. package/dist/ui/assets/{wc-detached-CVCrBego.js → wc-detached-Bw0mLsiP.js} +1 -1
  131. package/dist/ui/assets/wc-extension-DS-jtOJw.js +2 -0
  132. package/dist/ui/assets/wc-live-B3kXelNl.js +253 -0
  133. package/dist/ui/assets/wc-nav-tMI3vvSL.js +2 -0
  134. package/dist/ui/assets/wc-onboarding-Dm8BUVRJ.js +2 -0
  135. package/dist/ui/assets/wc-permissions-mVh0SOdZ.js +3 -0
  136. package/dist/ui/assets/wc-permissions-providers-BufEXbhX.js +2 -0
  137. package/dist/ui/assets/wc-permissions-registry-CR1rTd2H.js +1 -0
  138. package/dist/ui/assets/wc-permissions-registry-Dx-BRiKK.js +1 -0
  139. package/dist/ui/assets/wc-placeholder-DEo1-1V5.js +4 -0
  140. package/dist/ui/assets/wc-settings-D3QCZnur.js +24 -0
  141. package/dist/ui/assets/{wc-shell-Cl1ON8m4.js → wc-shell-DgOdmKyR.js} +603 -101
  142. package/dist/ui/assets/wc-sprinkles-D5si03_4.js +2 -0
  143. package/dist/ui/assets/wc-tray-lTqtwMFb.js +5 -0
  144. package/dist/ui/assets/whisper-engine-Emfsg6Pq.js +1 -0
  145. package/dist/ui/assets/whisper-session-Dt-B3KmT.js +2 -0
  146. package/dist/ui/assets/worker-D1KsWRGg.js +1 -1
  147. package/dist/ui/assets/writable-vfs-client-BKZGifKZ.js +1 -0
  148. package/dist/ui/assets/{xai-grok-Dk2TY3YV.js → xai-grok-D4a_2yZf.js} +1 -1
  149. package/dist/ui/assets/{xai-grok-3pMUa44B.js → xai-grok-Dpq_7ZV8.js} +1 -1
  150. package/dist/ui/electron-overlay-entry.css +37 -0
  151. package/dist/ui/electron-overlay-entry.js +5853 -222
  152. package/dist/ui/index.html +7 -3
  153. package/dist/ui/llm-proxy-sw.js +1 -1
  154. package/dist/ui/packages/webapp/index.html +7 -3
  155. package/dist/ui/preview-sw.js +1 -1
  156. package/package.json +6 -1
  157. package/dist/ui/assets/__vite-browser-external-CM3Eme-n.js +0 -1
  158. package/dist/ui/assets/account-store-BEhGWtfD.js +0 -99
  159. package/dist/ui/assets/account-store-Dd_leIXI.js +0 -38
  160. package/dist/ui/assets/adobe-D48uApVM.js +0 -2
  161. package/dist/ui/assets/adobe-n060f8AC.js +0 -1
  162. package/dist/ui/assets/backend-local-CMfz45hs.js +0 -9
  163. package/dist/ui/assets/biome_wasm-DEjrJMqV.js +0 -1
  164. package/dist/ui/assets/bsh-watchdog-DbLa5QsS.js +0 -74
  165. package/dist/ui/assets/cdn-url-builder-Cq7LLgru.js +0 -1
  166. package/dist/ui/assets/chunk-CMxvf4Kt.js +0 -1
  167. package/dist/ui/assets/common-ewBM5he1.js +0 -1
  168. package/dist/ui/assets/composer-speech-DYshRh0w.js +0 -1
  169. package/dist/ui/assets/dip-D889fVdn.js +0 -1
  170. package/dist/ui/assets/dist-DElqKLgF.js +0 -43
  171. package/dist/ui/assets/esp32-BTVUAltJ.js +0 -1
  172. package/dist/ui/assets/esp32c2-Cvhq9547.js +0 -1
  173. package/dist/ui/assets/esp32c3-D9lOBWYZ.js +0 -1
  174. package/dist/ui/assets/esp32c5-D2poFXyG.js +0 -1
  175. package/dist/ui/assets/esp32c6-Ct3LxBMw.js +0 -1
  176. package/dist/ui/assets/esp32c61-BsudQf3e.js +0 -1
  177. package/dist/ui/assets/esp32h2-CPO25efF.js +0 -1
  178. package/dist/ui/assets/esp32p4-GHiT8voO.js +0 -1
  179. package/dist/ui/assets/esp32s2-DrH4e7iJ.js +0 -1
  180. package/dist/ui/assets/esp32s3-BHbeScNz.js +0 -1
  181. package/dist/ui/assets/esp8266-DNz2CP4r.js +0 -1
  182. package/dist/ui/assets/github-CAYclPFE.js +0 -1
  183. package/dist/ui/assets/github-WET9dsz8.js +0 -2
  184. package/dist/ui/assets/hear-BOsXTls8.js +0 -1
  185. package/dist/ui/assets/hear-C4xX91lk.js +0 -1
  186. package/dist/ui/assets/js-realm-worker-BWFis6jC.js +0 -5
  187. package/dist/ui/assets/kernel-worker-BWuqWtNZ.js +0 -9820
  188. package/dist/ui/assets/kokoro-engine-CHIq9iyX.js +0 -2
  189. package/dist/ui/assets/kokoro-engine-DuH7Gw3x.js +0 -1
  190. package/dist/ui/assets/magick-wasm-BvNy9LFP.js +0 -1
  191. package/dist/ui/assets/main-xJNt0P6z.js +0 -24
  192. package/dist/ui/assets/mount-Blejt3yC.js +0 -1
  193. package/dist/ui/assets/mount-BpQ-KROh.js +0 -2
  194. package/dist/ui/assets/mount-id-DtHfwjbZ.js +0 -1
  195. package/dist/ui/assets/mount-picker-popup-BORxgnGu.js +0 -1
  196. package/dist/ui/assets/new-session-BKd3_lSr.js +0 -2
  197. package/dist/ui/assets/nodejs-BQ9Oov1A.js +0 -1
  198. package/dist/ui/assets/oauth-bootstrap--I6asg_u.js +0 -2
  199. package/dist/ui/assets/oauth-service-CoJ-pYVD.js +0 -1
  200. package/dist/ui/assets/oauth-service-DAzsBd4D.js +0 -1
  201. package/dist/ui/assets/panel-rpc-handlers-WBg5aql4.js +0 -2
  202. package/dist/ui/assets/preview-vfs-responder-dyQBWIl5.js +0 -1
  203. package/dist/ui/assets/provider-L4NElIF3.js +0 -2
  204. package/dist/ui/assets/provider-store-access-CmS1duqc.js +0 -1
  205. package/dist/ui/assets/provider-store-access-IaKurQe7.js +0 -1
  206. package/dist/ui/assets/proxied-fetch-BBO8HwcV.js +0 -1
  207. package/dist/ui/assets/py-realm-worker-BnG_DxBd.js +0 -242
  208. package/dist/ui/assets/remote-terminal-view-CSqiig2G.js +0 -6
  209. package/dist/ui/assets/remote-vfs-client-BwJhhtIV.js +0 -1
  210. package/dist/ui/assets/secret-env-DcfTe28K.js +0 -1
  211. package/dist/ui/assets/session-freezer-k9segaoE.js +0 -1
  212. package/dist/ui/assets/setup-sudo-CocP-GMB.js +0 -1
  213. package/dist/ui/assets/shared-B7TdM-Ik.js +0 -1
  214. package/dist/ui/assets/speak-BtCLW3r-.js +0 -1
  215. package/dist/ui/assets/speak-D511iTBd.js +0 -1
  216. package/dist/ui/assets/sudo-BGiCdyHh.js +0 -4
  217. package/dist/ui/assets/tool-ui-N76-EdVc.js +0 -1
  218. package/dist/ui/assets/tool-ui-YOPqzMmL.js +0 -1
  219. package/dist/ui/assets/transformers-env-6vj5CcH9.js +0 -1
  220. package/dist/ui/assets/transformers-env-BEJ8prM4.js +0 -1
  221. package/dist/ui/assets/transformers.web-C9_H_wuQ.js +0 -37
  222. package/dist/ui/assets/transformers.web-DJfCkvxA.js +0 -43
  223. package/dist/ui/assets/typescript-qBO0Z4wm.js +0 -448
  224. package/dist/ui/assets/wc-attach-B2r60IVZ.js +0 -13
  225. package/dist/ui/assets/wc-extension-Bj-WZK-v.js +0 -2
  226. package/dist/ui/assets/wc-live-692eqzbB.js +0 -253
  227. package/dist/ui/assets/wc-nav-KPH2me07.js +0 -2
  228. package/dist/ui/assets/wc-onboarding-BMIcqP11.js +0 -2
  229. package/dist/ui/assets/wc-placeholder-LktBOWny.js +0 -4
  230. package/dist/ui/assets/wc-settings-BZvKvAI9.js +0 -24
  231. package/dist/ui/assets/wc-sprinkles-C675pGpy.js +0 -2
  232. package/dist/ui/assets/wc-tray-DDcDU_hv.js +0 -5
  233. package/dist/ui/assets/web-BMWiUYJ-.js +0 -1
  234. package/dist/ui/assets/whisper-session-D89gZWHf.js +0 -2
  235. package/dist/ui/assets/writable-vfs-client-DMYsajEE.js +0 -1
package/README.md CHANGED
@@ -28,6 +28,20 @@ SLICC runs in a browser and controls the browser it runs in. It combines a shell
28
28
 
29
29
  > Status: active working prototype. The macOS app is the easiest way in today; and we have submitted the extension to Chrome Web Store.
30
30
 
31
+ ## Breaking release — thin extension + hosted webapp
32
+
33
+ This release splits the webapp out of the extension. The Chrome extension is now a thin **CDP pass-through bridge + per-page launcher**; the webapp UI and the agent engine load from the hosted leader tab (`https://www.sliccy.ai/?slicc=leader`, or `http://localhost:5710/?slicc=leader` for the `SLICC_EXT_DEV=1` dev build).
34
+
35
+ What changed:
36
+
37
+ - **Origin split.** The UI is served from `sliccy.ai` (or the local wrangler on `:8787`); the local CLI bridge runs on `:5710`. Page-side fetches to `/api/*` route to the bridge over CORS with an `X-Bridge-Token` header, never to the UI origin.
38
+ - **No more bundled side-panel UI or offscreen agent engine.** The extension does not ship `index.html` / `offscreen.html` / `sidepanel.html` anymore, and dropped the `sidePanel` and `offscreen` manifest permissions. The agent runs in the hosted leader tab.
39
+ - **One pinned hosted leader tab per Chrome profile.** The service worker creates and re-creates it; clicking the toolbar icon focuses it.
40
+ - **Per-page launcher overlay.** A content script injects `<slicc-launcher>` into every page; clicking it opens an iframe that auto-follows the leader.
41
+ - **Electron overlay is gone.** The standalone Electron flow now uses the same thin bridge model — the launched Electron app runs as a CDP target attached to a hosted leader.
42
+
43
+ If you previously loaded the extension from `dist/extension/` and used the side panel, you need to re-load the new build and use the pinned leader tab. State stored in the old extension origin (`chrome-extension://<id>/...`) does not migrate.
44
+
31
45
  ## Why SLICC is different
32
46
 
33
47
  - **Browser-native, not browser-adjacent.** The agent runtime lives in the browser, and the agent can act on the same browser it lives in. A great mix of power and containment. If you don't like what the AI does, close the browser tab and it's over.
@@ -35,7 +49,7 @@ SLICC runs in a browser and controls the browser it runs in. It combines a shell
35
49
  - **UI on the fly.** SLICC can generate rich user interfaces on the fly. These can be small visualizations in a chat response, or full-blown web applications that run in a sidebar, or even a separate tab.
36
50
  - **Built around Skills.** Agents don't suffer from missing capabilities, they suffer from skill issues. SLICC can install native skills into `/workspace/skills`, and it also discovers compatible `.agents` / `.claude` skills read-only across the reachable VFS.
37
51
  - **More than a coding panel.** Coding is one strong use case, but SLICC is built for practical browser work too: authenticated web apps, repetitive tab work, content operations, debugging, research, and automation.
38
- - **Works across runtimes.** Start in the CLI, run as a Chrome extension, connect multiple tray sessions, or attach to Electron apps with the same core model.
52
+ - **Works across runtimes.** Start in the CLI, run the thin Chrome extension that loads SLICC from `sliccy.ai`, connect multiple tray sessions, or attach to Electron apps with the same core model.
39
53
  - **Delegates in parallel.** The main agent can spin up isolated sub-agents for task-specific work instead of stuffing everything into one conversation.
40
54
 
41
55
  ## Who it is for
@@ -96,27 +110,14 @@ npm start
96
110
 
97
111
  ### 4. Chrome extension
98
112
 
99
- The extension runs the same core experience as a Chrome side panel with no separate server process.
113
+ The extension is a **thin CDP bridge + per-page launcher** no bundled UI, no offscreen agent engine. The full SLICC webapp loads from the hosted leader tab (`https://www.sliccy.ai/?slicc=leader`); the extension just pins that tab, proxies `chrome.debugger` to it, and injects a small `<slicc-launcher>` overlay into every page.
100
114
 
101
115
  ```bash
102
116
  npm install
103
117
  npm run build:extension
104
118
  ```
105
119
 
106
- Load `dist/extension/` as an unpacked extension in `chrome://extensions`, then open the SLICC side panel.
107
-
108
- **Pop out to a full-page tab.** In the Chrome extension, the side panel
109
- header has a "Pop out" button that opens SLICC in a full-page tab with
110
- the standalone split-pane layout. Close the tab to return to the side
111
- panel. State (chat history, scoops, VFS) is shared.
112
-
113
- While a detached tab is open, the side panel is disabled globally
114
- (across all Chrome windows) — clicking the toolbar icon focuses the
115
- detached tab. This is intentional; close the tab to restore the side
116
- panel. The optional best-effort SW hard-close of any open side panels
117
- uses `chrome.sidePanel.close` (Chrome 141+). Older Chrome versions
118
- still get full mutual exclusion via `window.close()` from the panel,
119
- the send-path lock, and the non-dismissible overlay.
120
+ Load `dist/extension/` as an unpacked extension in `chrome://extensions`. The service worker pins the hosted leader tab on install; clicking the toolbar icon focuses it (or recreates it if the user closed it). Each page you visit gets the launcher overlay, which iframes the same hosted webapp as an auto-follow follower.
120
121
 
121
122
  ### 5. Run a second browser
122
123
 
@@ -77,6 +77,32 @@ export declare function validateBridgeToken(presented: string | string[] | undef
77
77
  * — 122 bits of entropy is plenty for a session-scoped capability.
78
78
  */
79
79
  export declare function mintBridgeToken(): string;
80
+ /**
81
+ * Resolve the `/cdp` upgrade-gate token for this server process.
82
+ *
83
+ * Honors an inbound `SLICC_BRIDGE_TOKEN` env var (forwarded by
84
+ * `electron-main.ts` to the `--serve-only`/`--electron` child) so the
85
+ * gate is enforced even when `thinBridgeMode` is false. Falls back to
86
+ * minting a fresh token in `thinBridgeMode`, and to `null` (gate off)
87
+ * in the remaining legacy modes.
88
+ */
89
+ export declare function resolveServerBridgeToken(env: Record<string, string | undefined>, opts: {
90
+ thinBridgeMode: boolean;
91
+ }): string | null;
92
+ /**
93
+ * Decide whether the thin-bridge CORS + PNA middleware should be mounted on
94
+ * the `/api` surface.
95
+ *
96
+ * Mounted in canonical thin-bridge mode, and additionally whenever a
97
+ * per-process bridge token is present even with `thinBridgeMode` false (e.g.
98
+ * `--electron` with a forwarded `SLICC_BRIDGE_TOKEN`): the Electron overlay
99
+ * loads cross-origin from the hosted leader, so its `/api/runtime-config`
100
+ * fetch needs `access-control-*` headers. Mirrors the `/cdp` gate, which
101
+ * already honors a present token regardless of mode. Legacy dev /
102
+ * serve-only-without-token keep `bridgeToken === null` ⇒ CORS stays off ⇒
103
+ * same-origin behavior preserved.
104
+ */
105
+ export declare function shouldMountThinBridgeCors(thinBridgeMode: boolean, bridgeToken: string | null): boolean;
80
106
  /**
81
107
  * Parse the `Sec-WebSocket-Protocol` request header into a trimmed list. The
82
108
  * header is a comma-separated list per RFC 6455; `ws` exposes it raw.
@@ -123,8 +123,16 @@ const CORS_BASE_ALLOW_HEADERS = [
123
123
  * bridge (`decodeForbiddenResponseHeaders` reads `X-Proxy-Set-Cookie`).
124
124
  */
125
125
  const CORS_EXPOSE_HEADERS = 'Link, X-Proxy-Error, X-Proxy-Set-Cookie';
126
- /** Methods exposed to the hosted leader. */
127
- const CORS_ALLOW_METHODS = 'GET, POST, PUT, DELETE, OPTIONS';
126
+ /**
127
+ * Methods exposed to the hosted leader. Must cover the FULL `/api/fetch-proxy`
128
+ * verb set — `app.all('/api/fetch-proxy')` forwards any method, so the agent's
129
+ * `curl -X PROPFIND|REPORT|MKCALENDAR|PATCH …` reaches it cross-origin from
130
+ * sliccy.ai only if the browser's preflight sees the actual method advertised
131
+ * here. Standard CRUD verbs + WebDAV (RFC 4918) + CalDAV (RFC 4791). MUST stay
132
+ * byte-identical to Swift's `BridgeSecurity.corsAllowMethods` and to the Swift
133
+ * server's `fetchProxyMethods` list (`APIRoutes.swift`).
134
+ */
135
+ const CORS_ALLOW_METHODS = 'GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, MKCALENDAR, REPORT, COPY, MOVE, LOCK, UNLOCK';
128
136
  /**
129
137
  * True iff `origin` is in the bridge allowlist. The frozen prod base
130
138
  * (`BRIDGE_ALLOWED_ORIGINS`) is matched case-sensitively against the
@@ -195,6 +203,37 @@ export function validateBridgeToken(presented, expected) {
195
203
  export function mintBridgeToken() {
196
204
  return randomUUID();
197
205
  }
206
+ /**
207
+ * Resolve the `/cdp` upgrade-gate token for this server process.
208
+ *
209
+ * Honors an inbound `SLICC_BRIDGE_TOKEN` env var (forwarded by
210
+ * `electron-main.ts` to the `--serve-only`/`--electron` child) so the
211
+ * gate is enforced even when `thinBridgeMode` is false. Falls back to
212
+ * minting a fresh token in `thinBridgeMode`, and to `null` (gate off)
213
+ * in the remaining legacy modes.
214
+ */
215
+ export function resolveServerBridgeToken(env, opts) {
216
+ const fromEnv = env['SLICC_BRIDGE_TOKEN'];
217
+ if (fromEnv && fromEnv.length > 0)
218
+ return fromEnv;
219
+ return opts.thinBridgeMode ? mintBridgeToken() : null;
220
+ }
221
+ /**
222
+ * Decide whether the thin-bridge CORS + PNA middleware should be mounted on
223
+ * the `/api` surface.
224
+ *
225
+ * Mounted in canonical thin-bridge mode, and additionally whenever a
226
+ * per-process bridge token is present even with `thinBridgeMode` false (e.g.
227
+ * `--electron` with a forwarded `SLICC_BRIDGE_TOKEN`): the Electron overlay
228
+ * loads cross-origin from the hosted leader, so its `/api/runtime-config`
229
+ * fetch needs `access-control-*` headers. Mirrors the `/cdp` gate, which
230
+ * already honors a present token regardless of mode. Legacy dev /
231
+ * serve-only-without-token keep `bridgeToken === null` ⇒ CORS stays off ⇒
232
+ * same-origin behavior preserved.
233
+ */
234
+ export function shouldMountThinBridgeCors(thinBridgeMode, bridgeToken) {
235
+ return thinBridgeMode || bridgeToken !== null;
236
+ }
198
237
  /**
199
238
  * Parse the `Sec-WebSocket-Protocol` request header into a trimmed list. The
200
239
  * header is a comma-separated list per RFC 6455; `ws` exposes it raw.
@@ -26,7 +26,7 @@ interface FindChromeExecutableOptions {
26
26
  export declare function isCliProfileName(value: string | null | undefined): value is CliProfileName;
27
27
  export declare function resolveQaProfilesRoot(projectRoot: string): string;
28
28
  export declare function resolveProfilesDir(platform?: NodeJS.Platform, homeDir?: string, env?: NodeJS.ProcessEnv): string;
29
- export declare function resolveDefaultChromeUserDataDir(profilesDir?: string, servePort?: number): string;
29
+ export declare function resolveDefaultChromeUserDataDir(profilesDir?: string, servePort?: number, env?: NodeJS.ProcessEnv): string;
30
30
  /**
31
31
  * Builds the ordered list of legacy candidate paths to check during migration.
32
32
  * Checks $TMPDIR first (the macOS per-user temp dir used by terminal sessions),
@@ -137,6 +137,67 @@ export declare function waitForCdpPortFromStderr(child: ChildProcess, timeoutMs?
137
137
  * avoid but is not worth crashing over.
138
138
  */
139
139
  export declare function clearStaleDevToolsActivePort(userDataDir: string): Promise<void>;
140
+ /**
141
+ * Clear Chrome's "did not exit cleanly" flags so the next launch does NOT
142
+ * restore the previous session's tabs (or pop the crash-restore bubble).
143
+ *
144
+ * `npm run dev` is almost always stopped with Ctrl-C, which terminates the
145
+ * launched Chrome without a graceful shutdown. Chrome then records
146
+ * `profile.exit_type: "Crashed"` in `Default/Preferences` and, on the next
147
+ * launch against the same persistent profile, reopens the prior session's
148
+ * tabs. With the standalone single-client CDP proxy that is actively
149
+ * harmful: a *restored* duplicate webapp tab and the freshly-launched tab
150
+ * both dial `ws://<host>/cdp`, and because the proxy keeps only one client
151
+ * they evict each other in an endless ~5s reconnect war (each evicted page
152
+ * re-dials on the next leader-target refresh).
153
+ *
154
+ * Rewriting `exit_type` to `"Normal"` (and `exited_cleanly` to `true`) — the
155
+ * same trick ChromeDriver uses — before spawn makes Chrome believe the last
156
+ * session ended cleanly, so it starts fresh with a single tab.
157
+ *
158
+ * Best-effort and idempotent: a missing Preferences file (first run) is left
159
+ * absent — there is nothing to restore — and an unparseable one is left as-is
160
+ * for Chrome to regenerate. Never throws; a failure here must not block a
161
+ * launch.
162
+ */
163
+ export declare function clearChromeRestoreState(userDataDir: string): Promise<void>;
164
+ /**
165
+ * Remove Chrome's session-restore snapshot so a relaunch opens ONLY the
166
+ * command-line tab instead of also reopening the previous window's tabs.
167
+ *
168
+ * The dev launcher passes the UI URL on the command line, but Chrome ALSO
169
+ * restores `Default/Sessions/` from the prior run — so every `npm run dev` was
170
+ * adding a tab. `exit_type` only governs the *crash* bubble, not this; the fix
171
+ * is to drop the snapshot. Run before every spawn (like
172
+ * {@link clearStaleDevToolsActivePort}). Cookies / localStorage / IndexedDB
173
+ * live elsewhere and are untouched. Best-effort.
174
+ *
175
+ * Modern Chrome keeps the whole snapshot under `Default/Sessions/`; `Last
176
+ * Session` / `Last Tabs` are belt-and-suspenders for older Chromium builds that
177
+ * still wrote those two as top-level files under `Default/`.
178
+ */
179
+ export declare function clearChromeSessionRestore(userDataDir: string): Promise<void>;
180
+ /** Injectable seams for {@link terminateExistingProfileChrome} (tests). */
181
+ export interface ProfileChromeTerminationDeps {
182
+ readlinkImpl?: (path: string) => Promise<string>;
183
+ isAlive?: (pid: number) => boolean;
184
+ kill?: (pid: number, signal: NodeJS.Signals) => void;
185
+ sleep?: (ms: number) => Promise<void>;
186
+ }
187
+ /**
188
+ * Terminate a Chrome instance still holding this `user-data-dir`, then clear
189
+ * the stale Singleton lock files, so a fresh launch isn't refused.
190
+ *
191
+ * Chrome is single-instance per profile: a second `open -n` against a locked
192
+ * profile either exits non-zero ("Chrome exited … before reporting CDP port")
193
+ * or silently adds a tab to the running instance. On macOS the launcher starts
194
+ * Chrome via LaunchServices, so it isn't our child and Ctrl-C never reaps it —
195
+ * a leftover Chrome lingers across `npm run dev` runs. Chrome records the
196
+ * owning PID in the `SingletonLock` symlink (`<host>-<pid>`); if that process
197
+ * is alive we stop it (SIGTERM, then SIGKILL), then unlink the Singleton lock
198
+ * files. Best-effort and idempotent; a missing lock is a no-op.
199
+ */
200
+ export declare function terminateExistingProfileChrome(userDataDir: string, deps?: ProfileChromeTerminationDeps): Promise<void>;
140
201
  export interface ProbeCdpAliveOptions {
141
202
  /** Per-probe HTTP timeout in milliseconds. Default 500 ms. */
142
203
  timeoutMs?: number;
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync } from 'fs';
2
- import { cp, mkdir, readFile, rm, unlink, writeFile } from 'fs/promises';
2
+ import { cp, mkdir, readFile, readlink, rm, unlink, writeFile } from 'fs/promises';
3
3
  import { request as httpRequest } from 'http';
4
4
  import { homedir, platform as osPlatform, tmpdir } from 'os';
5
5
  import { dirname, join } from 'path';
@@ -85,7 +85,12 @@ export function resolveProfilesDir(platform = osPlatform(), homeDir = homedir(),
85
85
  }
86
86
  return tmpdir();
87
87
  }
88
- export function resolveDefaultChromeUserDataDir(profilesDir = resolveProfilesDir(), servePort) {
88
+ export function resolveDefaultChromeUserDataDir(profilesDir = resolveProfilesDir(), servePort, env = process.env) {
89
+ // Explicit override — e.g. SLICC_USER_DATA_DIR=$(mktemp -d) for a
90
+ // guaranteed-fresh profile without touching production data.
91
+ const explicit = env.SLICC_USER_DATA_DIR?.trim();
92
+ if (explicit)
93
+ return explicit;
89
94
  const suffix = servePort && servePort !== 5710 ? `-${servePort}` : '';
90
95
  return join(profilesDir, `${DEFAULT_USER_DATA_DIR_NAME}${suffix}`);
91
96
  }
@@ -521,6 +526,167 @@ export async function clearStaleDevToolsActivePort(userDataDir) {
521
526
  // Intentionally ignored — see docstring.
522
527
  }
523
528
  }
529
+ /**
530
+ * Clear Chrome's "did not exit cleanly" flags so the next launch does NOT
531
+ * restore the previous session's tabs (or pop the crash-restore bubble).
532
+ *
533
+ * `npm run dev` is almost always stopped with Ctrl-C, which terminates the
534
+ * launched Chrome without a graceful shutdown. Chrome then records
535
+ * `profile.exit_type: "Crashed"` in `Default/Preferences` and, on the next
536
+ * launch against the same persistent profile, reopens the prior session's
537
+ * tabs. With the standalone single-client CDP proxy that is actively
538
+ * harmful: a *restored* duplicate webapp tab and the freshly-launched tab
539
+ * both dial `ws://<host>/cdp`, and because the proxy keeps only one client
540
+ * they evict each other in an endless ~5s reconnect war (each evicted page
541
+ * re-dials on the next leader-target refresh).
542
+ *
543
+ * Rewriting `exit_type` to `"Normal"` (and `exited_cleanly` to `true`) — the
544
+ * same trick ChromeDriver uses — before spawn makes Chrome believe the last
545
+ * session ended cleanly, so it starts fresh with a single tab.
546
+ *
547
+ * Best-effort and idempotent: a missing Preferences file (first run) is left
548
+ * absent — there is nothing to restore — and an unparseable one is left as-is
549
+ * for Chrome to regenerate. Never throws; a failure here must not block a
550
+ * launch.
551
+ */
552
+ export async function clearChromeRestoreState(userDataDir) {
553
+ const prefsPath = join(userDataDir, 'Default', 'Preferences');
554
+ let raw;
555
+ try {
556
+ raw = await readFile(prefsPath, 'utf8');
557
+ }
558
+ catch {
559
+ return; // no Preferences yet (first run) — nothing to restore
560
+ }
561
+ let prefs;
562
+ try {
563
+ const parsed = JSON.parse(raw);
564
+ if (!isJsonObject(parsed))
565
+ return;
566
+ prefs = parsed;
567
+ }
568
+ catch {
569
+ return; // corrupt file — leave it for Chrome to regenerate
570
+ }
571
+ const profile = ensureObject(prefs, 'profile');
572
+ if (profile['exit_type'] === 'Normal' && profile['exited_cleanly'] === true) {
573
+ return; // already clean — avoid a needless rewrite
574
+ }
575
+ profile['exit_type'] = 'Normal';
576
+ profile['exited_cleanly'] = true;
577
+ try {
578
+ await writeJsonFile(prefsPath, prefs);
579
+ }
580
+ catch {
581
+ // Best-effort — a write failure just leaves the prior restore behavior.
582
+ }
583
+ }
584
+ /**
585
+ * Remove Chrome's session-restore snapshot so a relaunch opens ONLY the
586
+ * command-line tab instead of also reopening the previous window's tabs.
587
+ *
588
+ * The dev launcher passes the UI URL on the command line, but Chrome ALSO
589
+ * restores `Default/Sessions/` from the prior run — so every `npm run dev` was
590
+ * adding a tab. `exit_type` only governs the *crash* bubble, not this; the fix
591
+ * is to drop the snapshot. Run before every spawn (like
592
+ * {@link clearStaleDevToolsActivePort}). Cookies / localStorage / IndexedDB
593
+ * live elsewhere and are untouched. Best-effort.
594
+ *
595
+ * Modern Chrome keeps the whole snapshot under `Default/Sessions/`; `Last
596
+ * Session` / `Last Tabs` are belt-and-suspenders for older Chromium builds that
597
+ * still wrote those two as top-level files under `Default/`.
598
+ */
599
+ export async function clearChromeSessionRestore(userDataDir) {
600
+ const defaultDir = join(userDataDir, 'Default');
601
+ try {
602
+ await rm(join(defaultDir, 'Sessions'), { recursive: true, force: true });
603
+ }
604
+ catch {
605
+ // ignore
606
+ }
607
+ for (const name of ['Last Session', 'Last Tabs']) {
608
+ try {
609
+ await unlink(join(defaultDir, name));
610
+ }
611
+ catch {
612
+ // ignore (missing)
613
+ }
614
+ }
615
+ }
616
+ /**
617
+ * Terminate a Chrome instance still holding this `user-data-dir`, then clear
618
+ * the stale Singleton lock files, so a fresh launch isn't refused.
619
+ *
620
+ * Chrome is single-instance per profile: a second `open -n` against a locked
621
+ * profile either exits non-zero ("Chrome exited … before reporting CDP port")
622
+ * or silently adds a tab to the running instance. On macOS the launcher starts
623
+ * Chrome via LaunchServices, so it isn't our child and Ctrl-C never reaps it —
624
+ * a leftover Chrome lingers across `npm run dev` runs. Chrome records the
625
+ * owning PID in the `SingletonLock` symlink (`<host>-<pid>`); if that process
626
+ * is alive we stop it (SIGTERM, then SIGKILL), then unlink the Singleton lock
627
+ * files. Best-effort and idempotent; a missing lock is a no-op.
628
+ */
629
+ export async function terminateExistingProfileChrome(userDataDir, deps = {}) {
630
+ const readlinkImpl = deps.readlinkImpl ?? readlink;
631
+ const isAlive = deps.isAlive ?? defaultPidIsAlive;
632
+ const kill = deps.kill ?? ((pid, signal) => process.kill(pid, signal));
633
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
634
+ let pid = null;
635
+ try {
636
+ pid = parseSingletonLockPid(await readlinkImpl(join(userDataDir, 'SingletonLock')));
637
+ }
638
+ catch {
639
+ pid = null; // no SingletonLock — nothing holding the profile
640
+ }
641
+ if (pid !== null && isAlive(pid)) {
642
+ try {
643
+ kill(pid, 'SIGTERM');
644
+ }
645
+ catch {
646
+ // already gone between the alive-check and the signal
647
+ }
648
+ for (let i = 0; i < 30 && isAlive(pid); i++) {
649
+ await sleep(100);
650
+ }
651
+ if (isAlive(pid)) {
652
+ try {
653
+ kill(pid, 'SIGKILL');
654
+ }
655
+ catch {
656
+ // ignore
657
+ }
658
+ }
659
+ }
660
+ for (const name of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) {
661
+ try {
662
+ await unlink(join(userDataDir, name));
663
+ }
664
+ catch {
665
+ // ignore (missing)
666
+ }
667
+ }
668
+ }
669
+ /**
670
+ * `SingletonLock` is a symlink to `<hostname>-<pid>`. The hostname can contain
671
+ * dashes, so the PID is the segment after the LAST dash.
672
+ */
673
+ function parseSingletonLockPid(target) {
674
+ const dash = target.lastIndexOf('-');
675
+ if (dash < 0)
676
+ return null;
677
+ const pid = Number.parseInt(target.slice(dash + 1), 10);
678
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
679
+ }
680
+ function defaultPidIsAlive(pid) {
681
+ try {
682
+ process.kill(pid, 0);
683
+ return true;
684
+ }
685
+ catch (err) {
686
+ // ESRCH = no such process; EPERM = exists but not signalable by us.
687
+ return err.code === 'EPERM';
688
+ }
689
+ }
524
690
  /**
525
691
  * Single-shot HTTP probe of Chrome's `/json/version` endpoint. Resolves
526
692
  * `true` only when the port answers with a 2xx response whose body is
@@ -1,5 +1,57 @@
1
1
  import { type ChildProcess } from 'child_process';
2
2
  import { type ElectronInspectableTarget } from './electron-runtime.js';
3
+ /**
4
+ * Query param name used to mark the role of an overlay tab on the hosted
5
+ * launcher URL. The pinned leader carries `role=leader`; auto-follow
6
+ * followers carry `role=follower`. The hosted webapp interprets these to
7
+ * decide which tab anchors the CDP bridge vs. follows the leader.
8
+ */
9
+ export declare const BRIDGE_ROLE_QUERY_PARAM = "role";
10
+ export declare const BRIDGE_ROLE_LEADER = "leader";
11
+ export declare const BRIDGE_ROLE_FOLLOWER = "follower";
12
+ /**
13
+ * Thin-bridge coordinates for the Electron overlay. The injected overlay
14
+ * always loads from a hosted launcher (`hostedLeaderOrigin`, defaulting to
15
+ * production `https://www.sliccy.ai`) and dials back to the local `/cdp`
16
+ * WebSocket using the per-process bridge token. This is the only overlay
17
+ * path — the legacy bundled-UI overlay served from the local serve port
18
+ * was retired.
19
+ */
20
+ export interface ThinBridgeConfig {
21
+ hostedLeaderOrigin: string;
22
+ bridgeWsUrl: string;
23
+ bridgeToken: string;
24
+ }
25
+ export type OverlayRole = typeof BRIDGE_ROLE_LEADER | typeof BRIDGE_ROLE_FOLLOWER;
26
+ /**
27
+ * Build the thin-bridge config for the Electron overlay injector. The
28
+ * hosted-leader origin defaults to production `https://www.sliccy.ai`
29
+ * (overridable via `SLICC_HOSTED_LEADER_ORIGIN` / `WORKER_BASE_URL`), so
30
+ * the only genuinely unresolvable case is a missing per-process bridge
31
+ * token — in which case this returns `null` and the caller fails fast
32
+ * rather than falling back to a (now-retired) bundled overlay.
33
+ */
34
+ export declare function resolveOverlayThinBridge(env: Record<string, string | undefined>, bridgeToken: string | null, servePort: number): ThinBridgeConfig | null;
35
+ export interface ThinOverlayUrlOptions extends ThinBridgeConfig {
36
+ role: OverlayRole;
37
+ activeTab?: string;
38
+ }
39
+ /**
40
+ * Build the hosted launcher URL for an overlay injection. Mirrors the
41
+ * standalone Path A launch-URL shape (`bridge`, `bridgeToken` query
42
+ * params) with one Electron-specific addition: a `role` param that pins
43
+ * the first injected tab as the leader and marks every subsequent tab as
44
+ * an auto-follow follower.
45
+ */
46
+ export declare function buildThinOverlayAppUrl(opts: ThinOverlayUrlOptions): string;
47
+ /**
48
+ * Resolve the hosted leader origin Chrome / Electron should open in thin
49
+ * mode. Prefers explicit overrides (`SLICC_HOSTED_LEADER_ORIGIN`, then
50
+ * `WORKER_BASE_URL`) so dev can point at staging; defaults to production
51
+ * `https://www.sliccy.ai`. Trailing slashes are stripped so callers can
52
+ * safely concatenate paths.
53
+ */
54
+ export declare function resolveHostedLeaderOrigin(env?: Record<string, string | undefined>): string;
3
55
  interface RunningProcessInfo {
4
56
  pid: number;
5
57
  commandLine: string;
@@ -41,13 +93,43 @@ export declare function computeAverageLuminance(pixels: Buffer, width: number, h
41
93
  * actually needs unblocking when the parent is a local file.
42
94
  */
43
95
  export declare function resolveFetchProxyOrigin(targetUrl: string, servePort: number): string;
96
+ /**
97
+ * Pre-built bootstrap scripts for thin-mode injection — one per overlay
98
+ * role. The injector picks `leader` for the first injected target and
99
+ * `follower` for every subsequent target.
100
+ */
101
+ export interface ThinBootstrapSet {
102
+ leader: string;
103
+ follower: string;
104
+ }
105
+ /**
106
+ * JS probe that reports whether the overlay iframe actually loaded. Walks the
107
+ * `<slicc-launcher>` host's (open) shadow root to find the iframe
108
+ * depth-agnostically, then classifies by cross-origin reachability: the
109
+ * thin-bridge overlay is ALWAYS a different origin (hosted webapp) than the
110
+ * app document, so a committed cross-origin navigation makes
111
+ * `iframe.contentWindow.location.href` THROW — that throw is the ONLY success
112
+ * signal. Any READABLE href (`about:blank`, `''`, or a CSP-blocked swap to
113
+ * `chrome-error://chromewebdata/`) means the cross-origin nav did NOT commit,
114
+ * so the overlay did not load and the setBypassCSP escalation must fire.
115
+ * Returns `'ok'` only from the catch; otherwise `'no-host' / 'no-iframe' /
116
+ * 'no-src' / 'blank:<href>'`.
117
+ */
118
+ export declare const OVERLAY_LOADED_PROBE_EXPRESSION = "(function() {\n var host = document.getElementById('slicc-electron-overlay-root');\n if (!host || !host.shadowRoot) return 'no-host';\n var iframe = host.shadowRoot.querySelector('iframe');\n if (!iframe) return 'no-iframe';\n if (!iframe.src) return 'no-src';\n try {\n // Thin-bridge overlay is ALWAYS cross-origin (hosted webapp) vs the app\n // document. A committed cross-origin navigation makes this access THROW.\n // Any READABLE href means the cross-origin nav did NOT commit \u2014 still\n // about:blank, or swapped to chrome-error://chromewebdata/ by a CSP block \u2014\n // so the overlay did NOT load and the setBypassCSP escalation must fire.\n var href = iframe.contentWindow && iframe.contentWindow.location ? iframe.contentWindow.location.href : '';\n return 'blank:' + href;\n } catch (e) {\n return 'ok';\n }\n })()";
44
119
  export declare class ElectronOverlayInjector {
45
120
  private readonly cdpPort;
46
121
  private readonly servePort;
47
- private readonly bootstrapScript;
122
+ /** Thin-mode bootstrap pair — the only overlay path. */
123
+ private readonly thinBootstraps;
48
124
  private readonly probeDelayMs;
49
125
  private readonly connections;
50
126
  private readonly cspBypassedTargets;
127
+ /**
128
+ * URL of the target currently elected as the pinned leader. Cleared by
129
+ * `syncTargets` when that target disappears so the next injection
130
+ * re-elects a fresh leader.
131
+ */
132
+ private leaderTargetUrl;
51
133
  private syncTimer;
52
134
  private syncing;
53
135
  private constructor();
@@ -56,6 +138,13 @@ export declare class ElectronOverlayInjector {
56
138
  servePort: number;
57
139
  dev: boolean;
58
140
  projectRoot: string;
141
+ /**
142
+ * Thin-bridge coordinates: the overlay loads from the hosted launcher
143
+ * with bridge-URL + token query params and tabs are split into one
144
+ * pinned leader and N auto-follow followers. Required — the legacy
145
+ * bundled overlay path was retired.
146
+ */
147
+ thinBridge: ThinBridgeConfig;
59
148
  }): Promise<ElectronOverlayInjector>;
60
149
  /**
61
150
  * Test-only factory: skips bundle loading and lets tests drive the per-target
@@ -65,28 +154,49 @@ export declare class ElectronOverlayInjector {
65
154
  static _createForTesting(options: {
66
155
  cdpPort?: number;
67
156
  servePort: number;
68
- bootstrapScript?: string;
157
+ thinBootstraps?: ThinBootstrapSet;
69
158
  probeDelayMs?: number;
70
159
  }): ElectronOverlayInjector;
160
+ /** Test-only: snapshot the elected leader target URL (null when no leader). */
161
+ _testingLeaderTargetUrl(): string | null;
162
+ /** Test-only: seed the elected leader (drives the follower-election path). */
163
+ _testingSeedLeaderTargetUrl(url: string | null): void;
71
164
  /** Test-only: drive the per-target connect flow without going through `start`. */
72
165
  _testingConnectToTarget(target: ElectronInspectableTarget): void;
73
166
  /** Test-only: seed the per-target "already bypassed" guard. */
74
167
  _testingSeedBypassedTarget(url: string): void;
75
168
  /** Test-only: snapshot the per-target "already bypassed" guard set. */
76
169
  _testingBypassedTargets(): ReadonlySet<string>;
170
+ /** Test-only: drive a single `syncTargets` pass without `start()`'s interval. */
171
+ _testingSyncTargets(): Promise<void>;
77
172
  /** Test-only: close any sockets opened by `_testingConnectToTarget`. */
78
173
  _testingCloseConnections(): void;
79
174
  start(): Promise<void>;
80
175
  stop(): void;
81
176
  private syncTargets;
82
177
  /**
83
- * Check if the overlay iframe loaded successfully by evaluating a probe script.
84
- * Returns true if the iframe element exists and has started loading content.
178
+ * Check if the overlay iframe loaded successfully by evaluating a probe
179
+ * script. Walks the `<slicc-launcher>` host's (open) shadow root to find the
180
+ * iframe depth-agnostically and classifies by cross-origin reachability: the
181
+ * thin-bridge overlay is ALWAYS a different origin than the app document, so
182
+ * only a THROW on `iframe.contentWindow.location.href` (a committed
183
+ * cross-origin navigation) counts as loaded. Any readable href — including a
184
+ * CSP-blocked swap to `chrome-error://chromewebdata/` — means the nav did not
185
+ * commit, so the Fetch-proxy escalation must still fire.
186
+ * See {@link OVERLAY_LOADED_PROBE_EXPRESSION}.
85
187
  */
86
188
  private probeOverlayIframeLoaded;
189
+ /**
190
+ * Pick the bootstrap script for `target`, electing the leader on first
191
+ * use when thin-mode is active. Same target URL ↔ same role across
192
+ * reconnects so a page that bounces its CDP session stays the leader
193
+ * (no re-election on transient drops, only on `syncTargets` cleanup).
194
+ */
195
+ private resolveBootstrapForTarget;
87
196
  /**
88
197
  * Build a script that sets the SLICC theme preference in localStorage to
89
- * match the target app's detected theme, then runs the bootstrap.
198
+ * match the target app's detected theme, then runs the bootstrap. The
199
+ * bootstrap is target-specific (leader vs. follower).
90
200
  */
91
201
  private buildThemedBootstrap;
92
202
  /**