From ab0d7e904cb79cb1bcbc32cb15debbc3836a077c Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:15:20 +0200 Subject: [PATCH] fix: reloading the page or closing the terminal shell drops two tools and breaks the prompt cache With an Open Terminal connected to a chat, the tools for reading from and typing into the user's shell were only offered while the Shell tab was open in the browser. Reloading the page or closing the shell removed them from the next request, which changed the start of the tool list and made the provider's prompt cache miss for the whole chat. The two tools now stay offered in the chat for as long as the terminal is selected, and calling them with no shell open returns the existing "No connected user shell" result. Automations and sub-agents, which never have a shell, still do not get them. Fixes #31590 --- backend/open_webui/utils/middleware.py | 28 ++---------------------- src/lib/components/chat/XTerminal.svelte | 10 --------- src/lib/stores/index.ts | 3 --- src/routes/+layout.svelte | 14 ------------ 4 files changed, 2 insertions(+), 53 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 6a3e4b6f5b..55f036ab5a 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -3102,7 +3102,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): if name not in tools_dict: tools_dict[name] = tool_dict - # Only advertise user-shell tools when the originating browser has a connected shell. + # Automations and sub-agents never have a user's browser shell, so they never get these tools. shell_tools = { name: tool for name, tool in tools_dict.items() @@ -3118,32 +3118,8 @@ async def process_chat_payload(request, form_data, user, metadata, model): or (tool.get('direct') and tool.get('server', {}).get('url') == terminal_id) ) } - connected = False - if ( - selected - and event_caller - and metadata.get('session_id') - and metadata.get('chat_id') - and not metadata.get('automation_id') - and not metadata.get('internal') - ): - try: - state = await asyncio.wait_for( - event_caller( - { - 'type': 'request:terminal:state', - 'data': {'terminal_id': terminal_id, 'session_id': metadata['session_id']}, - } - ), - timeout=2, - ) - connected = isinstance(state, dict) and state.get('connected') is True - except Exception: - # Old/disconnected browsers cannot confirm availability; other tools still work. - pass - for name in shell_tools: - if not connected or name not in selected: + if name not in selected or metadata.get('automation_id') or metadata.get('internal'): tools_dict.pop(name) if tools_dict: diff --git a/src/lib/components/chat/XTerminal.svelte b/src/lib/components/chat/XTerminal.svelte index d9163812ed..e4e420068e 100644 --- a/src/lib/components/chat/XTerminal.svelte +++ b/src/lib/components/chat/XTerminal.svelte @@ -5,7 +5,6 @@ import { WebLinksAddon } from '@xterm/addon-web-links'; import '@xterm/xterm/css/xterm.css'; import { terminalRequest, type TerminalConnection } from '$lib/apis/terminal'; - import { connectedUserTerminals } from '$lib/stores'; export let connection: TerminalConnection; export let chatId: string | null = null; @@ -23,13 +22,8 @@ let pingInterval: ReturnType; let destroyed = false; let sessionId = ''; - const terminalOwner = Symbol(); function disconnect() { - connectedUserTerminals.update((entries) => { - entries.delete(terminalOwner); - return entries; - }); connected = false; connecting = false; clearInterval(pingInterval); @@ -75,10 +69,6 @@ ws?.send( JSON.stringify({ type: 'auth', token: connection.key.trim(), chat_id: chatId ?? '' }) ); - if (connection.selector && chatId) { - const shell = { terminalId: connection.selector, chatId }; - connectedUserTerminals.update((entries) => entries.set(terminalOwner, shell)); - } connected = true; connecting = false; fit(); diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 42ad30bb5a..f09e1046a9 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -146,9 +146,6 @@ export type FileNavOpenRequest = string | { path: string; page?: number | null } export const showFileNavPath: Writable = writable(null); export const showFileNavDir: Writable = writable(null); export const selectedTerminalId: Writable = writable(null); -export const connectedUserTerminals = writable( - new Map() -); export const artifactCode = writable(null); export const artifactContents = writable(null); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 1e8919a5c9..c66e066f2e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -31,7 +31,6 @@ channels, channelId, terminalServers, - connectedUserTerminals, showControls, showFileNavPath, showFileNavDir, @@ -562,19 +561,6 @@ }; const chatEventHandler = async (event, cb) => { - // Answer this session's availability check even when another chat is active. - if ( - event?.data?.type === 'request:terminal:state' && - event.data.data?.session_id === $socket?.id - ) { - cb?.({ - connected: [...$connectedUserTerminals.values()].some( - (shell) => - shell.terminalId === event.data.data?.terminal_id && shell.chatId === event.chat_id - ) - }); - return; - } const chat = $page.url.pathname.includes(`/c/${event.chat_id}`); // Skip events from temporary chats that are not the current chat.