From 66bb8c6f0473babfacedff60a0f0208e4ed464a3 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 11 Apr 2026 16:39:13 +0700 Subject: [PATCH] Add daemon MCP injection toggle to settings --- packages/app/src/hooks/use-daemon-config.ts | 71 ++++++++++++++++++++ packages/app/src/screens/settings-screen.tsx | 31 +++++++++ 2 files changed, 102 insertions(+) create mode 100644 packages/app/src/hooks/use-daemon-config.ts diff --git a/packages/app/src/hooks/use-daemon-config.ts b/packages/app/src/hooks/use-daemon-config.ts new file mode 100644 index 000000000..5c9e77012 --- /dev/null +++ b/packages/app/src/hooks/use-daemon-config.ts @@ -0,0 +1,71 @@ +import { useCallback, useEffect, useMemo } from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import type { + MutableDaemonConfig, + MutableDaemonConfigPatch, +} from "@server/shared/messages"; +import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; + +export function daemonConfigQueryKey(serverId: string | null) { + return ["daemon-config", serverId] as const; +} + +interface UseDaemonConfigResult { + config: MutableDaemonConfig | null; + isLoading: boolean; + patchConfig: (patch: MutableDaemonConfigPatch) => Promise; +} + +export function useDaemonConfig(serverId: string | null): UseDaemonConfigResult { + const queryClient = useQueryClient(); + const client = useHostRuntimeClient(serverId ?? ""); + const isConnected = useHostRuntimeIsConnected(serverId ?? ""); + const queryKey = useMemo(() => daemonConfigQueryKey(serverId), [serverId]); + + const configQuery = useQuery({ + queryKey, + enabled: Boolean(serverId && client && isConnected), + staleTime: Infinity, + queryFn: async () => { + if (!client) { + throw new Error("Host is not connected"); + } + const result = await client.getDaemonConfig(); + return result.config; + }, + }); + + useEffect(() => { + if (!client || !isConnected || !serverId) { + return; + } + + return client.on("status", (message) => { + if (message.type !== "status") { + return; + } + if (message.payload.status !== "daemon_config_changed") { + return; + } + queryClient.setQueryData(queryKey, message.payload.config as MutableDaemonConfig); + }); + }, [client, isConnected, queryClient, queryKey, serverId]); + + const patchConfig = useCallback( + async (patch: MutableDaemonConfigPatch) => { + if (!client) { + return undefined; + } + const result = await client.patchDaemonConfig(patch); + queryClient.setQueryData(queryKey, result.config); + return result.config; + }, + [client, queryClient, queryKey], + ); + + return { + config: configQuery.data ?? null, + isLoading: configQuery.isLoading, + patchConfig, + }; +} diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index f25aa969b..0c174c2c3 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -65,6 +65,7 @@ import { settingsStyles } from "@/styles/settings"; import { THINKING_TONE_NATIVE_PCM_BASE64 } from "@/utils/thinking-tone.native-pcm"; import { useVoiceAudioEngineOptional } from "@/contexts/voice-context"; import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon"; +import { useDaemonConfig } from "@/hooks/use-daemon-config"; import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot"; import { useIsCompactFormFactor } from "@/constants/layout"; import { AGENT_PROVIDER_DEFINITIONS } from "@server/server/agent/provider-manifest"; @@ -360,6 +361,7 @@ function HostsSection(props: HostsSectionProps) { } interface GeneralSectionProps { + routeServerId: string; settings: AppSettings; handleThemeChange: (theme: AppSettings["theme"]) => void; handleSendBehaviorChange: (behavior: SendBehavior) => void; @@ -404,11 +406,14 @@ const THEME_LABELS: Record = { }; function GeneralSection({ + routeServerId, settings, handleThemeChange, handleSendBehaviorChange, }: GeneralSectionProps) { const { theme } = useUnistyles(); + const isConnected = useHostRuntimeIsConnected(routeServerId); + const { config, patchConfig } = useDaemonConfig(routeServerId); const iconSize = theme.iconSize.md; const iconColor = theme.colors.foregroundMuted; @@ -475,6 +480,31 @@ function GeneralSection({ ]} /> + {routeServerId.length > 0 && isConnected ? ( + + + Inject Paseo tools + + Automatically inject Paseo MCP tools into new agents + + + { + void patchConfig({ + mcp: { + injectIntoAgents: value === "on", + }, + }); + }} + options={[ + { value: "on", label: "On" }, + { value: "off", label: "Off" }, + ]} + /> + + ) : null} ); @@ -1107,6 +1137,7 @@ export default function SettingsScreen() { }; const generalProps: GeneralSectionProps = { + routeServerId, settings, handleThemeChange, handleSendBehaviorChange,