From 6996c9dec0f7cc6105edd8c1fb20657bed2ac5c1 Mon Sep 17 00:00:00 2001 From: Jurij Skornik Date: Tue, 10 Feb 2026 16:39:48 +0100 Subject: [PATCH] Add "Show MCP tool execution panels" sub-setting Add a sub-setting under auto-approve in Settings > Tools & Plugins that controls whether tool execution panels are visible in chat when tools auto-execute. When hidden, tools execute silently with a continuous "Thinking..." indicator until the final response arrives. - Add showMcpToolExecutionPanels to Settings type (default: false) - Change autoApproveMcpTools default to true - Add disabled prop to Checkbox component - Expand McpAutoapproveForm to dual-checkbox with indented sub-setting - Auto-execute hidden tool calls via useEffect with ref-based guard - Skip rendering empty assistant messages (tool-calls only, no content) - Show continuous Thinking indicator during hidden tool execution - Disable input while hidden tools are in-flight --- apps/agent/src/app/(protected)/chat.tsx | 95 +++++++++++++++++-- apps/agent/src/app/(protected)/settings.tsx | 8 +- apps/agent/src/components/Checkbox.tsx | 3 + .../components/forms/McpAutoaproveForm.tsx | 55 +++++++++-- apps/agent/src/hooks/useSettings.tsx | 4 +- 5 files changed, 145 insertions(+), 20 deletions(-) diff --git a/apps/agent/src/app/(protected)/chat.tsx b/apps/agent/src/app/(protected)/chat.tsx index ec426e8..aa29b4d 100644 --- a/apps/agent/src/app/(protected)/chat.tsx +++ b/apps/agent/src/app/(protected)/chat.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { View, Platform, KeyboardAvoidingView, ScrollView } from "react-native"; import { Image } from "expo-image"; import * as Clipboard from "expo-clipboard"; @@ -52,6 +52,7 @@ export default function ChatPage() { const pendingToolCalls = useRef>(new Set()); // Track tool calls that need responses before calling LLM const toolKAContents = useRef>(new Map()); // Track KAs across tool calls in a single request + const dispatchedHiddenCalls = useRef>(new Set()); // Track auto-dispatched hidden tool calls const chatMessagesRef = useRef(null); @@ -92,6 +93,29 @@ export default function ChatPage() { }); } + // Auto-execute tool calls when panels are hidden (auto-approve on + show panels off). + // Deps intentionally exclude tools/callTool — dispatchedHiddenCalls ref prevents double-dispatch. + useEffect(() => { + for (const m of messages) { + if (m.role !== "assistant" || !m.tool_calls) continue; + for (const tc of m.tool_calls) { + const tcId = tc.id || ""; + if (!tcId) continue; + if (dispatchedHiddenCalls.current.has(tcId)) continue; + if (tools.getCallInfo(tcId)) continue; + + const isAutoApproved = + settings.autoApproveMcpTools || tools.isAllowedForSession(tc.name); + if (!isAutoApproved || settings.showMcpToolExecutionPanels) continue; + + dispatchedHiddenCalls.current.add(tcId); + tools.allowForSession(tc.name); + callTool({ ...tc, id: tcId }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [messages, settings.autoApproveMcpTools, settings.showMcpToolExecutionPanels]); + function addToolResultAndCheckCompletion(toolResult: ChatMessage) { const kaContents: any[] = []; const otherContents: any[] = []; @@ -277,6 +301,25 @@ export default function ChatPage() { [mcp, showAlert], ); + // True when hidden tool calls are in-flight (not yet dispatched or still loading) + const hasHiddenPendingTools = + !settings.showMcpToolExecutionPanels && + messages.some( + (m) => + m.role === "assistant" && + m.tool_calls?.some((tc) => { + const tcId = tc.id || ""; + if (!tcId) return false; + const info = tools.getCallInfo(tcId); + const isAutoApproved = + settings.autoApproveMcpTools || + tools.isAllowedForSession(tc.name); + return isAutoApproved && (!info || info.status === "loading"); + }), + ); + + const isBusy = isGenerating || hasHiddenPendingTools; + const isLandingScreen = !messages.length && !isNativeMobile; console.debug("Messages:", messages); console.debug("Tools (enabled):", tools.enabled); @@ -349,8 +392,33 @@ export default function ChatPage() { } } + // Skip assistant messages that have only hidden tool calls and no visible content + const hasToolCalls = !!m.tool_calls?.length; + const allToolCallsHidden = + hasToolCalls && + m.tool_calls!.every((tc) => { + const isAutoApproved = + settings.autoApproveMcpTools || + tools.isAllowedForSession(tc.name); + return ( + isAutoApproved && !settings.showMcpToolExecutionPanels + ); + }); + + const hasVisibleText = text.some((t) => t.trim()); + + if ( + allToolCallsHidden && + !hasVisibleText && + kas.length === 0 && + files.length === 0 && + images.length === 0 + ) { + return null; + } + const isLastMessage = i === messages.length - 1; - const isIdle = !isGenerating && !m.tool_calls?.length; + const isIdle = !isBusy && !m.tool_calls?.length; return ( )} ); })} - {isGenerating && } + {isBusy && } @@ -541,7 +620,7 @@ export default function ChatPage() { onToolServerTick={(_, enabled) => { tools.toggleAll(enabled); }} - disabled={isGenerating} + disabled={isBusy} style={[{ maxWidth: 800 }, isWeb && { pointerEvents: "auto" }]} /> diff --git a/apps/agent/src/app/(protected)/settings.tsx b/apps/agent/src/app/(protected)/settings.tsx index 2c172cf..7fa63f0 100644 --- a/apps/agent/src/app/(protected)/settings.tsx +++ b/apps/agent/src/app/(protected)/settings.tsx @@ -176,9 +176,10 @@ const sections = [ const { showDialog } = useDialog(); const update = useCallback( - async (value: boolean) => { + async (autoApprove: boolean, showPanels: boolean) => { try { - await settings.set("autoApproveMcpTools", value); + await settings.set("autoApproveMcpTools", autoApprove); + await settings.set("showMcpToolExecutionPanels", showPanels); await settings.reload(); showDialog({ type: "success", @@ -199,7 +200,8 @@ const sections = [ return ( ); diff --git a/apps/agent/src/components/Checkbox.tsx b/apps/agent/src/components/Checkbox.tsx index c985ac2..803e53f 100644 --- a/apps/agent/src/components/Checkbox.tsx +++ b/apps/agent/src/components/Checkbox.tsx @@ -10,12 +10,14 @@ export default function Checkbox( onValueChange?: (value: boolean) => void; style?: StyleProp; testID?: string; + disabled?: boolean; }>, ) { const colors = useColors(); return ( props.onValueChange?.(!props.value)} testID={props.testID} diff --git a/apps/agent/src/components/forms/McpAutoaproveForm.tsx b/apps/agent/src/components/forms/McpAutoaproveForm.tsx index f16110e..717812b 100644 --- a/apps/agent/src/components/forms/McpAutoaproveForm.tsx +++ b/apps/agent/src/components/forms/McpAutoaproveForm.tsx @@ -6,28 +6,34 @@ import Checkbox from "@/components/Checkbox"; import Button from "@/components/Button"; export default function McpAutoapproveForm({ - currentValue, + currentAutoApprove, + currentShowPanels, onSubmit, }: { - currentValue: boolean; - onSubmit: (value: boolean) => Promise; + currentAutoApprove: boolean; + currentShowPanels: boolean; + onSubmit: (autoApprove: boolean, showPanels: boolean) => Promise; }) { const colors = useColors(); - const [value, setValue] = useState(currentValue); + const [autoApprove, setAutoApprove] = useState(currentAutoApprove); + const [showPanels, setShowPanels] = useState(currentShowPanels); const [loading, setLoading] = useState(false); + const isDirty = + autoApprove !== currentAutoApprove || showPanels !== currentShowPanels; + const submit = useCallback(async () => { setLoading(true); try { - await onSubmit(value); + await onSubmit(autoApprove, showPanels); } finally { setLoading(false); } - }, [onSubmit, value]); + }, [onSubmit, autoApprove, showPanels]); return ( - + + + + + Show MCP tool execution panels + + + + Display tool name, inputs, and outputs in chat when tools run + automatically. + +