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:
@@ -132,6 +132,7 @@ export function OpenProjectScreen() {
|
||||
<CommunityLinks />
|
||||
</View>
|
||||
<PairDeviceModal
|
||||
serverId={localServerId ?? ""}
|
||||
visible={isPairDeviceOpen}
|
||||
onClose={handleClosePairDevice}
|
||||
testID="open-project-pair-device-modal"
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
FolderGit2,
|
||||
SquareTerminal,
|
||||
Code2,
|
||||
Smartphone,
|
||||
} from "lucide-react-native";
|
||||
import { DropdownTrigger } from "@/components/ui/dropdown-trigger";
|
||||
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||
@@ -95,6 +96,7 @@ import {
|
||||
} from "@/i18n/locales";
|
||||
import {
|
||||
HostConnectionsPage,
|
||||
HostPairDevicePage,
|
||||
HostAgentsPage,
|
||||
HostSettingsPage,
|
||||
HostProvidersPage,
|
||||
@@ -167,6 +169,7 @@ const HOST_SECTION_ITEMS: HostSectionItem[] = [
|
||||
{ id: "host", labelKey: "settings.hostSections.host", icon: Server },
|
||||
{ id: "projects", labelKey: "settings.hostSections.projects", icon: FolderGit2 },
|
||||
{ id: "connections", labelKey: "settings.hostSections.connections", icon: Network },
|
||||
{ id: "pair-device", labelKey: "openProject.tiles.pairDevice.title", icon: Smartphone },
|
||||
{ id: "agents", labelKey: "settings.hostSections.agents", icon: Bot },
|
||||
{ id: "workspaces", labelKey: "settings.hostSections.workspaces", icon: FolderGit2 },
|
||||
{ id: "providers", labelKey: "settings.hostSections.providers", icon: Boxes },
|
||||
@@ -183,6 +186,8 @@ function renderHostSettingsContent(
|
||||
return <ProjectsScreen serverId={view.serverId} />;
|
||||
case "connections":
|
||||
return <HostConnectionsPage serverId={view.serverId} />;
|
||||
case "pair-device":
|
||||
return <HostPairDevicePage serverId={view.serverId} />;
|
||||
case "agents":
|
||||
return <HostAgentsPage serverId={view.serverId} />;
|
||||
case "workspaces":
|
||||
@@ -1262,8 +1267,7 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti
|
||||
if (view.kind !== "host") {
|
||||
return;
|
||||
}
|
||||
const section: HostSectionSlug = view.section;
|
||||
const target = buildSettingsHostSectionRoute(serverId, section);
|
||||
const target = buildSettingsHostSectionRoute(serverId, view.section);
|
||||
if (isCompactLayout) {
|
||||
router.push(target);
|
||||
} else {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user