@openchambery/web 1.19.4 → 1.19.5-beta.10
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.
- package/dist/assets/{AgentAvatar-CVTgW_rx.js → AgentAvatar-BceI_qBC.js} +1 -1
- package/dist/assets/{AssistantView-D73E__f8.js → AssistantView-lSnZ7082.js} +1 -1
- package/dist/assets/{ChatContainer-pWolXKsP.js → ChatContainer-DJLqUdGE.js} +39 -39
- package/dist/assets/ContextPanel-CfO8NpOL.js +285 -0
- package/dist/assets/{DiagramView-BJBf7KGE.js → DiagramView-_LIlKVbO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Cy3sf2M5.js → FadeInOnReveal-Cj9ZC3Ab.js} +1 -1
- package/dist/assets/{Icon-BLWVqBee.js → Icon-Du1LX11Q.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-DpapNgFo.js +7 -0
- package/dist/assets/{MobileDetailNavigation-CUq86QUr.js → MobileDetailNavigation-Ct_3mr5K.js} +1 -1
- package/dist/assets/{MobileShareBridge-BMolTtSL.js → MobileShareBridge-7LjBYxHH.js} +2 -2
- package/dist/assets/MobileSurface-CuEJc-ya.js +2 -0
- package/dist/assets/{MultiRunWindow-Bv3moZF8.js → MultiRunWindow-BEM2Ucj4.js} +1 -1
- package/dist/assets/{OnboardingScreen-CTNi2zuA.js → OnboardingScreen-Czusf7rR.js} +1 -1
- package/dist/assets/{OverlayScrollbar-GQgJusKL.js → OverlayScrollbar-oOLMqq2s.js} +1 -1
- package/dist/assets/SettingsView-DUeyhTBn.js +25 -0
- package/dist/assets/{SettingsWindow-BMtwRXID.js → SettingsWindow-Qa8HXF_v.js} +1 -1
- package/dist/assets/{TerminalView-DwzG7zBO.js → TerminalView-CBSreprw.js} +1 -1
- package/dist/assets/{ToolOutputDialog-iYAB6WiK.js → ToolOutputDialog-CRIU0XUQ.js} +1 -1
- package/dist/assets/{angular-html-BXZQHcPZ.js → angular-html-CtgxlIZT.js} +1 -1
- package/dist/assets/{angular-ts-D8x7dmpg.js → angular-ts-DrvebXRV.js} +1 -1
- package/dist/assets/{apl-ugbjmfzm.js → apl-ct3-l_ov.js} +1 -1
- package/dist/assets/appThemeRegistry-CvvscooO.js +2 -0
- package/dist/assets/{astro-BrfzL78y.js → astro-9KCX_vNx.js} +1 -1
- package/dist/assets/{blade-D4cYRUdW.js → blade-DHzjLhcB.js} +1 -1
- package/dist/assets/{button-CeCkEpvn.js → button-p6vbd3r6.js} +1 -1
- package/dist/assets/{c-CMzMVpWc.js → c-CEfQ88z4.js} +1 -1
- package/dist/assets/{clipboard-BXC1vZwE.js → clipboard-Ca2PSbr_.js} +1 -1
- package/dist/assets/{cobol-DthQPUuS.js → cobol-ffQsCDPB.js} +1 -1
- package/dist/assets/{coffee-8Pqo9WtM.js → coffee-DtRj345i.js} +1 -1
- package/dist/assets/{collapsible-B-aagADh.js → collapsible-Cimoz0kx.js} +1 -1
- package/dist/assets/{cpp-D18L7odM.js → cpp-NWnna9VM.js} +1 -1
- package/dist/assets/{crystal-9GeLf5Ic.js → crystal-CnnqEH_e.js} +1 -1
- package/dist/assets/{css-CCmJvSl6.js → css-BRwHOW3t.js} +1 -1
- package/dist/assets/{desktopHosts-BrrYPqKq.js → desktopHosts-DceeVBky.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DR60DATT.js → desktopRelayRestore-B0MRdR5y.js} +1 -1
- package/dist/assets/{diagram-Cxma_IKI.js → diagram-DX_MPRs3.js} +1 -1
- package/dist/assets/{dialog-wraJHZQ2.js → dialog-B2ieeCSR.js} +1 -1
- package/dist/assets/{edge-BNU68miL.js → edge-BmL4YNvW.js} +1 -1
- package/dist/assets/{elixir-Clm-oycI.js → elixir-m9Mcv-AW.js} +1 -1
- package/dist/assets/{elm-DyuJMdF4.js → elm-Dazsuz2c.js} +1 -1
- package/dist/assets/{erb-BGQIoPeF.js → erb-CJ1X7LGG.js} +1 -1
- package/dist/assets/{es-DQqFAaWT.js → es-Bj9HwM-0.js} +3 -3
- package/dist/assets/{fileQueries-BJKLdYos.js → fileQueries-BDld6h3B.js} +1 -1
- package/dist/assets/{fr-CNfGRpc1.js → fr-BgEHZGug.js} +3 -3
- package/dist/assets/{git-rebase-ZHVbPCu8.js → git-rebase-DHqm8iFM.js} +1 -1
- package/dist/assets/{gitApi-C6XQwHcm.js → gitApi-BVB4hH6A.js} +3 -3
- package/dist/assets/{glimmer-js-Dx29eTyZ.js → glimmer-js-529w2Uvl.js} +1 -1
- package/dist/assets/{glimmer-ts-7AFohuRx.js → glimmer-ts-B38MdnnB.js} +1 -1
- package/dist/assets/{glsl-Nbih-1pG.js → glsl-IFfKD25X.js} +1 -1
- package/dist/assets/{graphql-IOG_4fSc.js → graphql-m-L9qWWB.js} +1 -1
- package/dist/assets/{hack-DyZU34CD.js → hack-S2S7pq0v.js} +1 -1
- package/dist/assets/{haml-mwEldSxy.js → haml-BE8HBwbH.js} +1 -1
- package/dist/assets/{handlebars-BRbCg-TR.js → handlebars-DPMWj4Da.js} +1 -1
- package/dist/assets/{html-derivative-DpusPW9p.js → html-derivative-D0yehinc.js} +1 -1
- package/dist/assets/{html-DH50wxss.js → html-gfIVmLgx.js} +1 -1
- package/dist/assets/{http-fM2Ft62a.js → http-Yj1uQSnx.js} +1 -1
- package/dist/assets/{hurl-C7ZRA11y.js → hurl-CiAC6gLf.js} +1 -1
- package/dist/assets/{input-CyG05w-V.js → input-gaFVNq1P.js} +1 -1
- package/dist/assets/insertionBoundaries-Cpj9r_o6.js +4 -0
- package/dist/assets/{ja-CQqehnVn.js → ja-clyxf6re.js} +3 -3
- package/dist/assets/{java-CuLv1JzP.js → java-lPyr66Qw.js} +1 -1
- package/dist/assets/{javascript-e9R9mij1.js → javascript-BlmKDQ-I.js} +1 -1
- package/dist/assets/{jinja-BqL4dpCe.js → jinja-DrvjNn4y.js} +1 -1
- package/dist/assets/{jison-BU1E246U.js → jison-BZ-k3PlS.js} +1 -1
- package/dist/assets/{json-CWUAz4dE.js → json-CaQi8cq5.js} +1 -1
- package/dist/assets/{jsx-Tb9QFw4P.js → jsx-kfzB3oTi.js} +1 -1
- package/dist/assets/{julia-C6q7nYBJ.js → julia-Bup0jpIM.js} +1 -1
- package/dist/assets/{just-CLHVCPQG.js → just-XMsX5iKL.js} +1 -1
- package/dist/assets/{ko-kmwi8UJD.js → ko-BZb3gnuq.js} +3 -3
- package/dist/assets/{latex-DzR96v9w.js → latex-CHhOHp9u.js} +1 -1
- package/dist/assets/{liquid-UjsTutWK.js → liquid-C90FVKxw.js} +1 -1
- package/dist/assets/{lua-laWCoC7H.js → lua-BXPwTBAD.js} +1 -1
- package/dist/assets/main-BsdsPsHT.js +29 -0
- package/dist/assets/{main-OM6EVqFo.js → main-CQLsl_QE.js} +2 -2
- package/dist/assets/markdown-shiki.worker-woF2hOAN.js +349 -0
- package/dist/assets/{markdown-worker-DqO-GnVj.js → markdown-worker-CoO82KYF.js} +4 -4
- package/dist/assets/{markdownTheme-H7_7lbuh.js → markdownTheme-St8zxDvJ.js} +1 -1
- package/dist/assets/{marko-Cw81uHQK.js → marko-oFkW-Ef7.js} +1 -1
- package/dist/assets/{mdc-5VRIrvpo.js → mdc-Dt17Fv3G.js} +1 -1
- package/dist/assets/{miniChat-CWJejsNH.js → miniChat-w-S3l9bR.js} +2 -2
- package/dist/assets/mobile-CHDToW-w.js +2 -0
- package/dist/assets/multirun-CMEZHI5g.js +4 -0
- package/dist/assets/{nginx-OjbRtDTh.js → nginx-LfNI-F2k.js} +1 -1
- package/dist/assets/{nim-BqzUQFfL.js → nim-so0j2_Ky.js} +1 -1
- package/dist/assets/{perl-Bba2cEgf.js → perl-DQcGQU9O.js} +1 -1
- package/dist/assets/{persistence-DE9RnVcA.js → persistence-CLUawIlm.js} +1 -1
- package/dist/assets/{php-CkVgF6HF.js → php-Dae1IcCM.js} +1 -1
- package/dist/assets/{pl-Blv5o2QA.js → pl-CwZzSvrm.js} +3 -3
- package/dist/assets/{platform-MjT8iqKB.js → platform-BrncW9ab.js} +1 -1
- package/dist/assets/{projectMeta-DCJQ6I3m.js → projectMeta-ZD-iXSOT.js} +2 -2
- package/dist/assets/{pt-BR-BK74t-Bd.js → pt-BR-6ali2BQX.js} +3 -3
- package/dist/assets/{pug-Dj44SzNY.js → pug-CaHd5xEa.js} +1 -1
- package/dist/assets/{qml-DETHcUPx.js → qml-lLqImktD.js} +1 -1
- package/dist/assets/{r-CrbPkwLJ.js → r-C2VuzPsf.js} +1 -1
- package/dist/assets/{radio-MAnQ7sDW.js → radio-B-nm8u03.js} +1 -1
- package/dist/assets/{razor-56IyEPA5.js → razor-DTpzsAil.js} +1 -1
- package/dist/assets/{regexp-BEMDgR1N.js → regexp-DJeMtopQ.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-65t0A6BD.js +2 -0
- package/dist/assets/renderMobileApp-a3Joz-yn.js +25 -0
- package/dist/assets/{rst-C8BEzC8U.js → rst-c6kvN9mK.js} +1 -1
- package/dist/assets/{ruby-DWyeL44t.js → ruby-B68kNTjT.js} +1 -1
- package/dist/assets/{runtimeConfig-C2Vygqku.js → runtimeConfig-BGuiq1aJ.js} +1 -1
- package/dist/assets/runtimeEndpointReset-HC9CW_hT.js +4 -0
- package/dist/assets/{sas-Dt-MhHv8.js → sas-Br3BRi6f.js} +1 -1
- package/dist/assets/{scss-jmTM-KWO.js → scss-ugvUHawi.js} +1 -1
- package/dist/assets/{shellscript-BgI_n_Bv.js → shellscript-Ci5rJl0t.js} +1 -1
- package/dist/assets/{shellsession-DhmUadx_.js → shellsession-Dxn1WKgk.js} +1 -1
- package/dist/assets/{shikiHighlight-Bpy8sf5r.js → shikiHighlight-DFiqYozo.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-BdbKDDAP.js → sortable-tabs-strip-CxLE1X9x.js} +1 -1
- package/dist/assets/{soy-qfQbh7dQ.js → soy-kgnQSRVZ.js} +1 -1
- package/dist/assets/{sql-CwyNz0Gh.js → sql-Cx3XaVwp.js} +1 -1
- package/dist/assets/src-D-LxNwlu.css +2 -0
- package/dist/assets/{stata-D9MlkA-a.js → stata-B5YhL3cp.js} +1 -1
- package/dist/assets/{store-UweBeN2y.js → store-BvrCRq6U.js} +3 -3
- package/dist/assets/{surrealql-xR93a923.js → surrealql-B8ysTfdK.js} +1 -1
- package/dist/assets/{svelte-BA7ahdky.js → svelte-D5-K3GME.js} +1 -1
- package/dist/assets/{templ-CxpzFYl8.js → templ-n2DDBrat.js} +1 -1
- package/dist/assets/{terminalApi-Bnx0KvaV.js → terminalApi-D2OzR0I3.js} +1 -1
- package/dist/assets/{tex-CePKgOWg.js → tex-CpNhXIXy.js} +1 -1
- package/dist/assets/{tooltip-B__x-HDJ.js → tooltip-D1a6rKLA.js} +1 -1
- package/dist/assets/{ts-tags-DP2fsj53.js → ts-tags-CrtENrUj.js} +1 -1
- package/dist/assets/{tsx-CI52QFY8.js → tsx-DEnzboO-.js} +1 -1
- package/dist/assets/{twig-BjoebDgo.js → twig-B7PeheP0.js} +1 -1
- package/dist/assets/{typescript-cpHrN9Ya.js → typescript-CUlPIybL.js} +1 -1
- package/dist/assets/{uk-6I-KXy3R.js → uk-qRqJs4GS.js} +3 -3
- package/dist/assets/{url-CTuw7Rjf.js → url-zwuNq3GS.js} +1 -1
- package/dist/assets/{useAppFontEffects-BVVxQm3q.js → useAppFontEffects-pF6rKdKM.js} +4 -4
- package/dist/assets/{useAssistantUIStore-D2K7ksGj.js → useAssistantUIStore-D699Sbdb.js} +1 -1
- package/dist/assets/{useDesktopWindowControlsLayout-BR2bI_26.js → useDesktopWindowControlsLayout-DH7Om-wY.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-DdS6rQm9.js → useEffectiveDirectory-BvZFK9wU.js} +1 -1
- package/dist/assets/{useFontPreferences-CWZnNRjz.js → useFontPreferences-F2QPNE2V.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-DFSb2gzX.js → useMobileNavigationStore-CGOeJb9D.js} +10 -10
- package/dist/assets/{useSessionAutoCleanup-BNC1lVkC.js → useSessionAutoCleanup-lBn8oZ8f.js} +1 -1
- package/dist/assets/{useUIStore-DOxLVeGb.js → useUIStore-BXjMQrel.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-B9mnTmvs.js → useWorkerHighlightedLines-Bc1NJlYO.js} +2 -2
- package/dist/assets/{utils-C2R0YoC5.js → utils-D94hxQYq.js} +1 -1
- package/dist/assets/{vue-DK5KPkZ5.js → vue-C6ZZy2AI.js} +1 -1
- package/dist/assets/{vue-html-BlUa8woS.js → vue-html-DsvGlIY2.js} +1 -1
- package/dist/assets/{vue-vine-D3hlJyLi.js → vue-vine-C8VLalyn.js} +1 -1
- package/dist/assets/{xml-CspvrcYx.js → xml-ChNx2zKg.js} +1 -1
- package/dist/assets/{xsl-D10hdnv5.js → xsl-yWKkpZli.js} +1 -1
- package/dist/assets/{yaml-C00-kDDU.js → yaml-CENNGtdf.js} +1 -1
- package/dist/assets/{zh-CN-DwlzS4yK.js → zh-CN-CwZ0adcY.js} +3 -3
- package/dist/assets/{zh-TW-p0VriyfT.js → zh-TW-DC0jiCzA.js} +3 -3
- package/dist/index.html +9 -9
- package/dist/mini-chat.html +9 -9
- package/dist/mobile.html +9 -9
- package/package.json +1 -1
- package/server/index.js +40 -3
- package/server/lib/notifications/DOCUMENTATION.md +2 -2
- package/server/lib/notifications/push-runtime.js +4 -14
- package/server/lib/notifications/runtime.js +11 -20
- package/server/lib/notifications/runtime.test.js +4 -6
- package/server/lib/opencode/core-routes.js +56 -23
- package/server/lib/opencode/core-routes.test.js +206 -6
- package/server/lib/relay/DOCUMENTATION.md +2 -1
- package/server/lib/relay/service.js +223 -71
- package/server/lib/relay/service.test.js +96 -13
- package/dist/assets/ContextPanel-GBq5lSES.js +0 -285
- package/dist/assets/MarkdownRendererImpl-BUXY9iuu.js +0 -7
- package/dist/assets/MobileSurface-DDRPrlAq.js +0 -2
- package/dist/assets/SettingsView-Ip_jVXoD.js +0 -25
- package/dist/assets/appThemeRegistry-rHYlrhb5.js +0 -2
- package/dist/assets/insertionBoundaries-DG_1_ENK.js +0 -4
- package/dist/assets/main-MkZ3xcbE.js +0 -29
- package/dist/assets/markdown-shiki.worker-CKKjvdhc.js +0 -12
- package/dist/assets/mobile-D-ubuubW.js +0 -2
- package/dist/assets/multirun-YB7NFEGX.js +0 -4
- package/dist/assets/renderElectronMiniChatApp-BPZ5LfbX.js +0 -2
- package/dist/assets/renderMobileApp-pqjbEzlN.js +0 -23
- package/dist/assets/runtimeEndpointReset-CuW2UDPM.js +0 -4
- package/dist/assets/src-eGuNWxHc.css +0 -2
package/dist/index.html
CHANGED
|
@@ -552,25 +552,25 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-CQLsl_QE.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
559
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
559
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-BXjMQrel.js">
|
|
560
560
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
561
561
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
562
562
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
563
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
564
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
565
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
563
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CLUawIlm.js">
|
|
564
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-DceeVBky.js">
|
|
565
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-B0MRdR5y.js">
|
|
566
566
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
567
567
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
568
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
569
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
568
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-BrncW9ab.js">
|
|
569
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-D2OzR0I3.js">
|
|
570
570
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
571
571
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
572
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
573
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
572
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BGuiq1aJ.js">
|
|
573
|
+
<link rel="stylesheet" crossorigin href="/assets/src-D-LxNwlu.css">
|
|
574
574
|
</head>
|
|
575
575
|
<body class="h-full bg-background text-foreground">
|
|
576
576
|
<!-- Keep the startup layer outside #root. React replaces every child of
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,25 +74,25 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-w-S3l9bR.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
81
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
81
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-BXjMQrel.js">
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
85
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
87
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
85
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CLUawIlm.js">
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-DceeVBky.js">
|
|
87
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-B0MRdR5y.js">
|
|
88
88
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
89
89
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
91
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-BrncW9ab.js">
|
|
91
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-D2OzR0I3.js">
|
|
92
92
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
93
93
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
94
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
95
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BGuiq1aJ.js">
|
|
95
|
+
<link rel="stylesheet" crossorigin href="/assets/src-D-LxNwlu.css">
|
|
96
96
|
</head>
|
|
97
97
|
<body class="h-full bg-background text-foreground">
|
|
98
98
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,25 +63,25 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-CHDToW-w.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
70
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
70
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-BXjMQrel.js">
|
|
71
71
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
72
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
73
73
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
74
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
75
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
76
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
74
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CLUawIlm.js">
|
|
75
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-DceeVBky.js">
|
|
76
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-B0MRdR5y.js">
|
|
77
77
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
79
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
80
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
79
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-BrncW9ab.js">
|
|
80
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-D2OzR0I3.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
84
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
83
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BGuiq1aJ.js">
|
|
84
|
+
<link rel="stylesheet" crossorigin href="/assets/src-D-LxNwlu.css">
|
|
85
85
|
</head>
|
|
86
86
|
<body class="h-full bg-background text-foreground">
|
|
87
87
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -342,8 +342,6 @@ const updateUiVisibility = (token, visible, platform) => {
|
|
|
342
342
|
if (visible === true) clearPendingPushBadge();
|
|
343
343
|
return pushRuntime.updateUiVisibility(token, visible, platform);
|
|
344
344
|
};
|
|
345
|
-
const isAnyUiVisible = (...args) => pushRuntime.isAnyUiVisible(...args);
|
|
346
|
-
const isAnyInteractiveClientVisible = (...args) => pushRuntime.isAnyInteractiveClientVisible(...args);
|
|
347
345
|
const isUiVisible = (...args) => pushRuntime.isUiVisible(...args);
|
|
348
346
|
const ensurePushInitialized = (...args) => pushRuntime.ensurePushInitialized(...args);
|
|
349
347
|
const setPushInitialized = (...args) => pushRuntime.setPushInitialized(...args);
|
|
@@ -664,7 +662,6 @@ const notificationTriggerRuntime = createNotificationTriggerRuntime({
|
|
|
664
662
|
sendPushToAllUiSessions,
|
|
665
663
|
sendApnsToAllUiSessions,
|
|
666
664
|
sendLiveActivityEnd,
|
|
667
|
-
isAnyInteractiveClientVisible,
|
|
668
665
|
buildOpenCodeUrl,
|
|
669
666
|
getOpenCodeAuthHeaders,
|
|
670
667
|
});
|
|
@@ -1419,6 +1416,29 @@ async function main(options = {}) {
|
|
|
1419
1416
|
// Re-evaluate the relay lifecycle after pairing/device changes (a revoked or
|
|
1420
1417
|
// redeemed device can flip relay demand on or off).
|
|
1421
1418
|
reconcileRelay: () => (relayServiceInstance ? relayServiceInstance.reconcile() : Promise.resolve()),
|
|
1419
|
+
// Canonical endpoints the host relay currently uses (primary first).
|
|
1420
|
+
// Pairing-session requests from paired desktop clients may carry a relayUrl
|
|
1421
|
+
// only when it matches one of these (an echo of an advertised endpoint,
|
|
1422
|
+
// never a re-point or an extension).
|
|
1423
|
+
getEffectiveRelayUrls: async () => {
|
|
1424
|
+
if (!relayServiceInstance) return [];
|
|
1425
|
+
try {
|
|
1426
|
+
const status = await relayServiceInstance.getStatus();
|
|
1427
|
+
return Array.isArray(status?.relayUrls) ? status.relayUrls : [];
|
|
1428
|
+
} catch {
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
// Every configured relay endpoint as a pairing candidate (multi-relay),
|
|
1433
|
+
// for the connection-candidates refresh.
|
|
1434
|
+
getRelayPairingCandidates: async () => {
|
|
1435
|
+
if (!relayServiceInstance) return [];
|
|
1436
|
+
try {
|
|
1437
|
+
return await relayServiceInstance.getPairingCandidates();
|
|
1438
|
+
} catch {
|
|
1439
|
+
return [];
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1422
1442
|
getPairingTransports: async (req) => {
|
|
1423
1443
|
const transports = resolvePairingTransports(req);
|
|
1424
1444
|
if (!relayServiceInstance || !transports.relayAvailable) return transports;
|
|
@@ -1426,6 +1446,7 @@ async function main(options = {}) {
|
|
|
1426
1446
|
return {
|
|
1427
1447
|
...transports,
|
|
1428
1448
|
relayUrl: relay.relayUrl,
|
|
1449
|
+
relayUrls: relay.relayUrls,
|
|
1429
1450
|
relayUrlLocked: relay.relayUrlLocked,
|
|
1430
1451
|
};
|
|
1431
1452
|
},
|
|
@@ -1516,6 +1537,22 @@ async function main(options = {}) {
|
|
|
1516
1537
|
onRelayUrlChanged: async () => {
|
|
1517
1538
|
await apnsRuntime.reRegisterAllTokens();
|
|
1518
1539
|
},
|
|
1540
|
+
// Owner gate for relay-endpoint management: UI session or the local desktop
|
|
1541
|
+
// shell. Paired remote clients (desktops included) are NOT endpoint owners
|
|
1542
|
+
// — adding/removing endpoints is the same trust boundary as re-pointing
|
|
1543
|
+
// the primary endpoint.
|
|
1544
|
+
isOwnerRequest: async (req, res) => {
|
|
1545
|
+
if (typeof uiAuthController?.resolveAuthContext !== 'function') return false;
|
|
1546
|
+
try {
|
|
1547
|
+
const context = await uiAuthController.resolveAuthContext(req, res, { allowClientAuth: true, allowUrlToken: false });
|
|
1548
|
+
if (context?.type === 'session') return true;
|
|
1549
|
+
if (context?.type === 'client') {
|
|
1550
|
+
return context.client?.clientKind === 'desktop-local';
|
|
1551
|
+
}
|
|
1552
|
+
} catch {
|
|
1553
|
+
}
|
|
1554
|
+
return false;
|
|
1555
|
+
},
|
|
1519
1556
|
});
|
|
1520
1557
|
relayServiceInstance = relayService;
|
|
1521
1558
|
relayService.registerRoutes(app);
|
|
@@ -52,7 +52,7 @@ This module provides notification message preparation utilities for the web serv
|
|
|
52
52
|
- session meta cache for child-session and small-model suppression
|
|
53
53
|
- template resolution and fallback behavior
|
|
54
54
|
- native notification fanout and web push payload fanout
|
|
55
|
-
- push
|
|
55
|
+
- push always fans out to every subscribed surface; another client's visibility never suppresses delivery
|
|
56
56
|
|
|
57
57
|
### Push runtime API (push-runtime.js)
|
|
58
58
|
- `createPushRuntime(dependencies)`: creates runtime for web push and UI visibility state.
|
|
@@ -62,7 +62,7 @@ This module provides notification message preparation utilities for the web serv
|
|
|
62
62
|
- `setPushInitialized(value)`
|
|
63
63
|
- `addOrUpdatePushSubscription(uiSessionToken, subscription, userAgent)`
|
|
64
64
|
- `removePushSubscription(uiSessionToken, endpoint)`
|
|
65
|
-
- `sendPushToAllUiSessions(payload
|
|
65
|
+
- `sendPushToAllUiSessions(payload)`
|
|
66
66
|
- `updateUiVisibility(token, visible)`
|
|
67
67
|
- `isAnyUiVisible()`
|
|
68
68
|
- `isUiVisible(token)`
|
|
@@ -221,8 +221,7 @@ export const createPushRuntime = (deps) => {
|
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
-
const sendPushToAllUiSessions = async (payload
|
|
225
|
-
const requireNoSse = options.requireNoSse === true;
|
|
224
|
+
const sendPushToAllUiSessions = async (payload) => {
|
|
226
225
|
const store = await readPushSubscriptionsFromDisk();
|
|
227
226
|
const sessions = store.subscriptionsBySession || {};
|
|
228
227
|
const subscriptionsByEndpoint = new Map();
|
|
@@ -239,14 +238,6 @@ export const createPushRuntime = (deps) => {
|
|
|
239
238
|
}
|
|
240
239
|
|
|
241
240
|
await Promise.all(Array.from(subscriptionsByEndpoint.values()).map(async (sub) => {
|
|
242
|
-
if (requireNoSse) {
|
|
243
|
-
// Mobile PWA subscriptions follow the same presence model as native push: suppress only
|
|
244
|
-
// when an interactive (desktop/web) client is visible. The phone PWA's own foreground is
|
|
245
|
-
// handled in the service worker (focused-client check), so it won't double-notify.
|
|
246
|
-
// Non-mobile (desktop/web) subscriptions keep the existing any-visible gate.
|
|
247
|
-
const suppressed = isMobilePlatform(sub.platform) ? isAnyInteractiveClientVisible() : isAnyUiVisible();
|
|
248
|
-
if (suppressed) return;
|
|
249
|
-
}
|
|
250
241
|
await sendPushToSubscription(sub, payload);
|
|
251
242
|
}));
|
|
252
243
|
};
|
|
@@ -278,10 +269,9 @@ export const createPushRuntime = (deps) => {
|
|
|
278
269
|
return false;
|
|
279
270
|
};
|
|
280
271
|
|
|
281
|
-
// True when at least one NON-mobile client (desktop/web/vscode) is currently visible.
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// phone's own (a backgrounded WKWebView can't report "hidden" before iOS suspends it).
|
|
272
|
+
// True when at least one NON-mobile client (desktop/web/vscode) is currently visible.
|
|
273
|
+
// Not a push gate — delivery always fans out. Kept as a visibility query for tests and
|
|
274
|
+
// diagnostics. Deliberately based on the desktop's visibility, never the phone's own.
|
|
285
275
|
const isAnyInteractiveClientVisible = () => {
|
|
286
276
|
const now = Date.now();
|
|
287
277
|
pruneUiVisibility(now);
|
|
@@ -12,7 +12,6 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
12
12
|
sendPushToAllUiSessions,
|
|
13
13
|
sendApnsToAllUiSessions,
|
|
14
14
|
sendLiveActivityEnd,
|
|
15
|
-
isAnyInteractiveClientVisible,
|
|
16
15
|
buildOpenCodeUrl,
|
|
17
16
|
getOpenCodeAuthHeaders,
|
|
18
17
|
} = deps;
|
|
@@ -68,25 +67,17 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
68
67
|
};
|
|
69
68
|
|
|
70
69
|
// Fan a notification out to every delivery channel: browser web-push (full templated
|
|
71
|
-
// payload) and native iOS APNs (generic model-based text). Both share the dedup tag
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}),
|
|
83
|
-
interactiveVisible
|
|
84
|
-
? Promise.resolve()
|
|
85
|
-
: Promise.resolve(sendApnsToAllUiSessions?.(toApnsGenericPayload(payload), options)).catch((error) => {
|
|
86
|
-
console.warn('[APNs] fanout failed:', error?.message ?? error);
|
|
87
|
-
}),
|
|
88
|
-
]);
|
|
89
|
-
};
|
|
70
|
+
// payload) and native iOS APNs (generic model-based text). Both share the dedup tag;
|
|
71
|
+
// a failure in one channel must not block the other. Visibility on any other client
|
|
72
|
+
// never suppresses delivery — every subscribed surface gets the push.
|
|
73
|
+
const fanoutPush = (payload, options) => Promise.all([
|
|
74
|
+
Promise.resolve(sendPushToAllUiSessions?.(payload, options)).catch((error) => {
|
|
75
|
+
console.warn('[Push] web-push fanout failed:', error?.message ?? error);
|
|
76
|
+
}),
|
|
77
|
+
Promise.resolve(sendApnsToAllUiSessions?.(toApnsGenericPayload(payload), options)).catch((error) => {
|
|
78
|
+
console.warn('[APNs] fanout failed:', error?.message ?? error);
|
|
79
|
+
}),
|
|
80
|
+
]);
|
|
90
81
|
|
|
91
82
|
let getIsWindowFocused = typeof deps.getIsWindowFocused === 'function'
|
|
92
83
|
? deps.getIsWindowFocused
|
|
@@ -28,7 +28,6 @@ const createRuntime = (overrides = {}) => {
|
|
|
28
28
|
sendPushToAllUiSessions,
|
|
29
29
|
sendApnsToAllUiSessions,
|
|
30
30
|
sendLiveActivityEnd,
|
|
31
|
-
isAnyInteractiveClientVisible: () => false,
|
|
32
31
|
buildOpenCodeUrl: (path) => `http://opencode.test${path}`,
|
|
33
32
|
getOpenCodeAuthHeaders: () => ({}),
|
|
34
33
|
getIsWindowFocused: () => false,
|
|
@@ -224,14 +223,13 @@ describe('notification trigger live activity end', () => {
|
|
|
224
223
|
expect(sendApnsToAllUiSessions).not.toHaveBeenCalled();
|
|
225
224
|
});
|
|
226
225
|
|
|
227
|
-
it('
|
|
226
|
+
it('still sends native push when another client is visible', async () => {
|
|
228
227
|
vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse()));
|
|
229
|
-
const { runtime, sendLiveActivityEnd, sendApnsToAllUiSessions } = createRuntime(
|
|
230
|
-
isAnyInteractiveClientVisible: () => true,
|
|
231
|
-
});
|
|
228
|
+
const { runtime, sendLiveActivityEnd, sendApnsToAllUiSessions, sendPushToAllUiSessions } = createRuntime();
|
|
232
229
|
await runtime.maybeSendPushForTrigger(completionPayload());
|
|
233
230
|
expect(sendLiveActivityEnd).toHaveBeenCalledWith({ sessionId: 'ses_root', status: 'complete' });
|
|
234
|
-
expect(
|
|
231
|
+
expect(sendPushToAllUiSessions).toHaveBeenCalledTimes(1);
|
|
232
|
+
expect(sendApnsToAllUiSessions).toHaveBeenCalledTimes(1);
|
|
235
233
|
});
|
|
236
234
|
|
|
237
235
|
it('suppresses live activity end for child and small-model sessions', async () => {
|
|
@@ -390,6 +390,16 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
390
390
|
// hostEncPubJwk, priority }) when the host relay is enabled, else null.
|
|
391
391
|
// Injected lazily because the relay service is constructed after these routes.
|
|
392
392
|
getRelayPairingCandidate = async () => null,
|
|
393
|
+
// All configured relay endpoints (canonical ws(s)://…), primary first, or
|
|
394
|
+
// [] when relay status is unavailable. Gates remote-bearer pairing requests
|
|
395
|
+
// that carry a relayUrl: paired desktops may only use an endpoint the host
|
|
396
|
+
// already has (echo of any configured endpoint, never a re-point).
|
|
397
|
+
getEffectiveRelayUrls = async () => [],
|
|
398
|
+
// Candidates for EVERY configured relay endpoint (multi-relay), or null /
|
|
399
|
+
// a non-array when only the single-endpoint accessor is available. Feeds
|
|
400
|
+
// the connection-candidates refresh so paired devices learn the full
|
|
401
|
+
// reachable endpoint set.
|
|
402
|
+
getRelayPairingCandidates = async () => null,
|
|
393
403
|
// Re-evaluate the relay lifecycle after pairing/device changes.
|
|
394
404
|
reconcileRelay = async () => {},
|
|
395
405
|
// Returns { local, lan, relayAvailable } — the direct transport URLs the
|
|
@@ -424,6 +434,15 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
424
434
|
}
|
|
425
435
|
};
|
|
426
436
|
|
|
437
|
+
// Which paired clients may act as operators of this server's device fleet:
|
|
438
|
+
// create pairing links, list every device, and revoke other devices. The
|
|
439
|
+
// desktop shell's local client is the in-process operator; a paired `desktop`
|
|
440
|
+
// client is granted the same trust at pairing time (parity with the SSH
|
|
441
|
+
// attach flow, where the connecting desktop authenticates as the operator).
|
|
442
|
+
// Mobile (and any other) paired clients stay scoped to their own record.
|
|
443
|
+
const canManageRemoteClients = (client) =>
|
|
444
|
+
client?.clientKind === 'desktop-local' || client?.clientKind === 'desktop';
|
|
445
|
+
|
|
427
446
|
const runWithClientManagementAuth = async (req, res, next, handler) => {
|
|
428
447
|
try {
|
|
429
448
|
if (typeof uiAuthController.resolveAuthContext === 'function') {
|
|
@@ -458,7 +477,7 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
458
477
|
}
|
|
459
478
|
if (context?.type === 'client') {
|
|
460
479
|
const client = await clientRecordFromAuthContext(context);
|
|
461
|
-
if (client
|
|
480
|
+
if (canManageRemoteClients(client)) {
|
|
462
481
|
await handler({ ...context, client });
|
|
463
482
|
return;
|
|
464
483
|
}
|
|
@@ -779,10 +798,10 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
779
798
|
await runWithClientManagementAuth(req, res, next, async (authContext) => {
|
|
780
799
|
if (authContext.type === 'client') {
|
|
781
800
|
const client = await clientRecordFromAuthContext(authContext);
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
if (client
|
|
801
|
+
// Operator clients (local desktop shell, paired desktops) manage the
|
|
802
|
+
// whole device fleet; every other client token is scoped to its own
|
|
803
|
+
// record.
|
|
804
|
+
if (!canManageRemoteClients(client)) {
|
|
786
805
|
return res.json({ clients: client ? [client] : [] });
|
|
787
806
|
}
|
|
788
807
|
}
|
|
@@ -807,9 +826,9 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
807
826
|
await runWithClientManagementAuth(req, res, next, async (authContext) => {
|
|
808
827
|
if (authContext.type === 'client') {
|
|
809
828
|
const actingClient = await clientRecordFromAuthContext(authContext);
|
|
810
|
-
//
|
|
811
|
-
// tokens may only revoke themselves.
|
|
812
|
-
if (actingClient
|
|
829
|
+
// Operator clients (local desktop shell, paired desktops) may revoke
|
|
830
|
+
// any device; other client tokens may only revoke themselves.
|
|
831
|
+
if (!canManageRemoteClients(actingClient)) {
|
|
813
832
|
const clientId = clientIdFromAuthContext(authContext);
|
|
814
833
|
if (!clientId || clientId !== req.params?.id) {
|
|
815
834
|
return res.status(403).json({ revoked: false, error: 'Client tokens can only revoke themselves' });
|
|
@@ -829,9 +848,10 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
829
848
|
await runWithClientManagementAuth(req, res, next, async (authContext) => {
|
|
830
849
|
if (authContext.type === 'client') {
|
|
831
850
|
const actingClient = await clientRecordFromAuthContext(authContext);
|
|
832
|
-
// Purging revoked devices is a whole-server management action; only
|
|
833
|
-
//
|
|
834
|
-
|
|
851
|
+
// Purging revoked devices is a whole-server management action; only
|
|
852
|
+
// operator clients (local desktop shell, paired desktops) or a UI
|
|
853
|
+
// session may do it.
|
|
854
|
+
if (!canManageRemoteClients(actingClient)) {
|
|
835
855
|
return res.status(403).json({ purged: 0, error: 'Client tokens cannot purge revoked devices' });
|
|
836
856
|
}
|
|
837
857
|
}
|
|
@@ -843,22 +863,25 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
843
863
|
|
|
844
864
|
app.post('/api/client-auth/pairing/sessions', express.json({ limit: '64kb' }), async (req, res, next) => {
|
|
845
865
|
await runWithClientCreateAuth(req, res, next, async (authContext) => {
|
|
846
|
-
// Changing the Host Relay endpoint is an owner-UI or local desktop-shell
|
|
847
|
-
// action. Paired remote clients never reach this handler (create-auth only
|
|
848
|
-
// admits session + desktop-local); keep the kind check so a future auth
|
|
849
|
-
// widening cannot re-point the Host relay from a remote bearer.
|
|
850
|
-
if (req.body?.relayUrl !== undefined && authContext?.type === 'client') {
|
|
851
|
-
const actingClient = authContext.client || await clientRecordFromAuthContext(authContext);
|
|
852
|
-
if (actingClient?.clientKind !== 'desktop-local') {
|
|
853
|
-
return res.status(403).json({ error: 'Only the owner UI session can set a custom Relay endpoint' });
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
866
|
const requestedRelayUrl = req.body?.relayUrl === undefined
|
|
857
867
|
? undefined
|
|
858
868
|
: normalizeRequestedRelayUrl(req.body.relayUrl);
|
|
859
869
|
if (req.body?.relayUrl !== undefined && !requestedRelayUrl) {
|
|
860
870
|
return res.status(400).json({ error: 'Relay URL must use ws:// or wss://' });
|
|
861
871
|
}
|
|
872
|
+
// Changing the Host Relay endpoint set is an owner-UI or local
|
|
873
|
+
// desktop-shell action. Paired desktop operators reach this handler too,
|
|
874
|
+
// but may only name an endpoint the host already uses — anything else
|
|
875
|
+
// would re-point (or extend) the Host relay from a remote bearer.
|
|
876
|
+
if (requestedRelayUrl !== undefined && authContext?.type === 'client') {
|
|
877
|
+
const actingClient = authContext.client || await clientRecordFromAuthContext(authContext);
|
|
878
|
+
if (actingClient?.clientKind !== 'desktop-local') {
|
|
879
|
+
const effectiveRelayUrls = await getEffectiveRelayUrls();
|
|
880
|
+
if (!Array.isArray(effectiveRelayUrls) || !effectiveRelayUrls.includes(requestedRelayUrl)) {
|
|
881
|
+
return res.status(403).json({ error: 'Only the owner UI session can set a custom Relay endpoint' });
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
862
885
|
const candidates = await pairingServerCandidates(req, {
|
|
863
886
|
preferredServerUrl: req.body?.serverUrl,
|
|
864
887
|
includeRelay: typeof req.body?.includeRelay === 'boolean' ? req.body.includeRelay : undefined,
|
|
@@ -906,8 +929,18 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
906
929
|
if (normalized) candidates.push({ type: 'lan', url: normalized, priority: 10 });
|
|
907
930
|
}
|
|
908
931
|
try {
|
|
909
|
-
|
|
910
|
-
|
|
932
|
+
// Multi-relay: a paired device learns EVERY configured endpoint so a
|
|
933
|
+
// lost/expensive domain can fall back to the other (the client still
|
|
934
|
+
// keeps its per-connection instance identity — serverId + relayUrl).
|
|
935
|
+
const relayCandidates = await getRelayPairingCandidates();
|
|
936
|
+
if (Array.isArray(relayCandidates)) {
|
|
937
|
+
for (const relayCandidate of relayCandidates) {
|
|
938
|
+
if (relayCandidate) candidates.push(relayCandidate);
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
const relayCandidate = await getRelayPairingCandidate({ ensureEnabled: false });
|
|
942
|
+
if (relayCandidate) candidates.push(relayCandidate);
|
|
943
|
+
}
|
|
911
944
|
} catch {
|
|
912
945
|
// Relay status failure must not break the direct-candidate refresh.
|
|
913
946
|
}
|