Make relay opt-in when pairing devices (#2706)

* feat: make relay opt-in for device pairing

* fix(relay): keep pairing state authoritative

* fix(relay): refresh pairing state across transports

* fix(onboard): honor explicit relay opt-out

* fix(relay): surface transport startup failures

* fix(pairing): gate offers for older daemons

* test(cli): rely on real pairing transport coverage

* fix(cli): report live relay without a pid file

* fix(relay): synchronize config during daemon startup

* fix(relay): preserve mutability for ignored overrides

* fix(cli): use reachable daemon for pairing

* fix(app): redirect invalid pair device routes

* fix(pairing): support remote host device pairing

Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals.

* fix(pairing): validate pairing destinations

Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state.

* fix(pairing): render QR codes across platforms

Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack.

* fix(cli): pair from persisted relay state

Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
This commit is contained in:
Mohamed Boudra
2026-08-01 04:35:06 +08:00
committed by GitHub
parent 3778d0ad7b
commit d2de309fc3
73 changed files with 2163 additions and 543 deletions
@@ -9,6 +9,7 @@ import {
function makeConfig(browserToolsEnabled = false): MutableDaemonConfig {
return {
relay: { enabled: false },
mcp: { injectIntoAgents: false },
browserTools: { enabled: browserToolsEnabled },
providers: {},
@@ -237,9 +237,7 @@ function HostConnectionError({ serverId }: { serverId: string }) {
}
export function HostConnectionsPage({ serverId }: { serverId: string }) {
const { t } = useTranslation();
const host = useHostProfile(serverId);
const isLocalDaemon = useIsLocalDaemon(serverId);
if (!host) {
return <HostNotFound />;
@@ -249,15 +247,25 @@ export function HostConnectionsPage({ serverId }: { serverId: string }) {
<View>
<HostConnectionError serverId={serverId} />
<ConnectionsSection host={host} />
{isLocalDaemon ? (
<SettingsSection title={t("settings.host.pairDevices.title")}>
<PairDeviceRow />
</SettingsSection>
) : null}
</View>
);
}
export function HostPairDevicePage({ serverId }: { serverId: string }) {
const { t } = useTranslation();
const host = useHostProfile(serverId);
if (!host) {
return <HostNotFound />;
}
return (
<SettingsSection title={t("settings.host.pairDevices.title")}>
<PairDeviceRow serverId={serverId} />
</SettingsSection>
);
}
export function HostAgentsPage({ serverId }: { serverId: string }) {
const { t } = useTranslation();
const host = useHostProfile(serverId);
@@ -1241,7 +1249,7 @@ function AppendSystemPromptCard({ serverId }: { serverId: string }) {
);
}
function PairDeviceRow() {
function PairDeviceRow({ serverId }: { serverId: string }) {
const { t } = useTranslation();
const { theme } = useUnistyles();
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -1265,6 +1273,7 @@ function PairDeviceRow() {
</Pressable>
<PairDeviceModal
serverId={serverId}
visible={isModalOpen}
onClose={handleClose}
testID="host-page-pair-device-card"
@@ -304,6 +304,7 @@ const disabledCodexEntry: ProviderSnapshotEntry = {
function makeConfig(providers: MutableDaemonConfig["providers"] = {}): MutableDaemonConfig {
return {
relay: { enabled: false },
mcp: { injectIntoAgents: false },
browserTools: { enabled: false },
providers,