feat: open a Browser tab automatically when the AI uses browser tools

Browser tools used by the AI failed with "No Browser tab is open in this chat" until the user opened a Browser tab by hand. Now the Browser tab opens automatically right before the tool runs, so the call works straight away. If a tab is already open nothing changes.
This commit is contained in:
Classic298 2026-11-05 19:48:23 +01:00
parent c165ee14f0
commit 30fba23b3f
4 changed files with 33 additions and 2 deletions

View file

@ -3150,6 +3150,17 @@ async def execute_tool_call(form_data, metadata, event_caller, tool_call):
):
return params, 'Error: tool call was not approved.', tool, tool_type, direct_tool
if (
event_caller
and name.startswith('browser_')
and (tool_type == 'terminal' or tool.get('server', {}).get('is_terminal') is True)
):
try:
async with asyncio.timeout(15):
await event_caller({'type': 'terminal:browser', 'data': {}})
except TimeoutError:
pass
tool_call['started_at'] = time.time()
started = time.perf_counter()
try:

View file

@ -1278,7 +1278,12 @@
});
};
const terminalEventHandler = (type: string, data: any, message: any) => {
const terminalEventHandler = async (type: string, data: any, message: any, cb?: Function) => {
if (type === 'terminal:browser') {
await terminalTabs?.ensureBrowserTab();
cb?.(true);
return;
}
const attachment = history.messages[message?.parentId]?.files?.find(
(file: any) => file.type === 'filesystem'
);
@ -1784,7 +1789,7 @@
eventConfirmationInputType = data?.input?.type ?? data?.type ?? '';
eventConfirmationInputOptions = data?.input?.options ?? data?.options ?? [];
} else if (type.startsWith('terminal:')) {
terminalEventHandler(type, data, message);
terminalEventHandler(type, data, message, cb);
} else {
console.log('Unknown message type', data);
}

View file

@ -557,6 +557,17 @@
connection: connection ?? undefined
});
};
const readyBrowserTabs = new Set<string>();
export async function ensureBrowserTab() {
const findBrowserTab = () =>
tabs.tabs.find((tab) => tab.kind === 'browser' && tab.terminalId === $selectedTerminalId);
if (!findBrowserTab()) openBrowser();
for (let waited = 0; waited < 10_000; waited += 100) {
const browserTab = findBrowserTab();
if (!browserTab || readyBrowserTabs.has(browserTab.id)) return;
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
const closeTab = async (id: string) => {
if (id === mainChatTabId) return;
if (closing.has(id)) return;
@ -575,6 +586,7 @@
if (tab.kind === 'artifacts') showArtifacts.set(false);
if (tab.kind === 'preview') showEmbeds.set(false);
tabs = removeTab(tabs, id);
readyBrowserTabs.delete(id);
delete dirtyFiles[id];
delete fileContexts[id];
if (!tabs.tabs.length) showTabs.set(false);
@ -1184,6 +1196,7 @@
active={!!$showTabs && visibleTabIds.has(tab.id)}
initialUrl={tab.path ?? ''}
onOpenBrowser={(url) => openBrowser(undefined, tab.terminalId, tab.browserBaseUrl, url)}
onReady={() => readyBrowserTabs.add(tab.id)}
overlay={dragged}
onNavigate={(path, label) => {
tabs = {

View file

@ -27,6 +27,7 @@
export let overlay = false;
export let onNavigate: (url: string, label: string) => void = () => {};
export let onOpenBrowser: (url: string) => void = () => {};
export let onReady: () => void = () => {};
export let onClose: (() => void) | null = null;
let session: BrowserSession | null = null;
let sessionChatId = chatId;
@ -270,6 +271,7 @@
return;
}
await tick();
onReady();
} catch (reason) {
error = 'Browser sessions unavailable. Update Open Terminal. ' + String(reason);
return;