From 59194d1502003d7f22ac023e7395e01a14c785cc Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Wed, 17 Jun 2026 19:18:23 +0000 Subject: [PATCH] Refine Browser naming and managed MCP UX - Rename Browser Use surfaces to Browser - Register Browser MCP under the new server name - Mark CloudCLI-managed MCP servers read-only - Adjust MCP stdio framing and sidebar footer sizing --- server/browser-use-mcp.ts | 54 +++++++---------- server/cli.js | 4 +- server/index.js | 6 +- .../browser-use/browser-use-mcp.routes.ts | 6 +- .../modules/browser-use/browser-use.routes.ts | 8 +-- .../browser-use/browser-use.service.ts | 49 +++++++-------- .../tests/browser-use.service.test.ts | 2 +- .../modules/providers/services/mcp.service.ts | 26 ++++++++ src/components/app/AppContent.tsx | 3 +- .../browser-use/view/BrowserUsePanel.tsx | 25 ++++---- src/components/main-content/types/types.ts | 3 +- .../view/subcomponents/MainContentTitle.tsx | 2 +- src/components/mcp/view/McpServers.tsx | 60 ++++++++++++------- .../settings/constants/constants.ts | 2 +- .../BrowserUseSettingsTab.tsx | 16 ++--- .../view/subcomponents/SidebarFooter.tsx | 26 ++++---- src/i18n/locales/en/settings.json | 6 +- 17 files changed, 166 insertions(+), 132 deletions(-) diff --git a/server/browser-use-mcp.ts b/server/browser-use-mcp.ts index 55c448ad..238bff35 100644 --- a/server/browser-use-mcp.ts +++ b/server/browser-use-mcp.ts @@ -53,7 +53,7 @@ async function callBrowserUseApi(toolName: string, input: Record) { - const payload = JSON.stringify(message); - process.stdout.write(`Content-Length: ${Buffer.byteLength(payload, 'utf8')}\r\n\r\n${payload}`); + // MCP stdio transport uses newline-delimited JSON (one JSON-RPC message per line, + // no embedded newlines). This is NOT the LSP Content-Length framing. + process.stdout.write(`${JSON.stringify(message)}\n`); } function sendResult(id: string | number | null | undefined, result: unknown) { @@ -352,33 +353,18 @@ function sendError(id: string | number | null | undefined, error: unknown) { }); } -let buffer = Buffer.alloc(0); +let buffer = ''; process.stdin.on('data', (chunk) => { - buffer = Buffer.concat([buffer, chunk]); - while (true) { - const headerEnd = buffer.indexOf('\r\n\r\n'); - if (headerEnd === -1) { - return; - } - - const header = buffer.slice(0, headerEnd).toString('utf8'); - const lengthMatch = /Content-Length:\s*(\d+)/i.exec(header); - if (!lengthMatch) { - buffer = buffer.slice(headerEnd + 4); + buffer += chunk.toString('utf8'); + let newlineIndex: number; + while ((newlineIndex = buffer.indexOf('\n')) !== -1) { + const rawMessage = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + if (!rawMessage) { continue; } - const length = Number.parseInt(lengthMatch[1], 10); - const messageStart = headerEnd + 4; - const messageEnd = messageStart + length; - if (buffer.length < messageEnd) { - return; - } - - const rawMessage = buffer.slice(messageStart, messageEnd).toString('utf8'); - buffer = buffer.slice(messageEnd); - void (async () => { let request: JsonRpcRequest; try { diff --git a/server/cli.js b/server/cli.js index 83c04411..0be3bf72 100755 --- a/server/cli.js +++ b/server/cli.js @@ -8,7 +8,7 @@ * (no args) - Start the server (default) * start - Start the server * sandbox - Manage Docker sandbox environments - * browser-use-mcp - Run Browser Use MCP stdio server + * browser-use-mcp - Run Browser MCP stdio server * status - Show configuration and data locations * help - Show help information * version - Show version information @@ -157,7 +157,7 @@ Usage: Commands: start Start the CloudCLI server (default) sandbox Manage Docker sandbox environments - browser-use-mcp Run the Browser Use MCP stdio server + browser-use-mcp Run the Browser MCP stdio server status Show configuration and data locations update Update to the latest version help Show this help information diff --git a/server/index.js b/server/index.js index ce35c542..f4c9e25e 100755 --- a/server/index.js +++ b/server/index.js @@ -196,10 +196,10 @@ app.use('/api/gemini', authenticateToken, geminiRoutes); // Plugins API Routes (protected) app.use('/api/plugins', authenticateToken, pluginsRoutes); -// Browser Use MCP bridge API (local token protected) +// Browser MCP bridge API (local token protected) app.use('/api/browser-use-mcp', browserUseMcpRoutes); -// Browser Use API Routes (protected) +// Browser API Routes (protected) app.use('/api/browser-use', authenticateToken, browserUseRoutes); // Unified provider MCP routes (protected) @@ -1717,7 +1717,7 @@ async function startServer() { try { await browserUseService.stopAllSessions(); } catch (err) { - console.error('[Browser Use] Error stopping sessions during shutdown:', err?.message || err); + console.error('[Browser] Error stopping sessions during shutdown:', err?.message || err); } try { await stopAllPlugins(); diff --git a/server/modules/browser-use/browser-use-mcp.routes.ts b/server/modules/browser-use/browser-use-mcp.routes.ts index 2899fd74..060bb028 100644 --- a/server/modules/browser-use/browser-use-mcp.routes.ts +++ b/server/modules/browser-use/browser-use-mcp.routes.ts @@ -16,7 +16,7 @@ router.use((req, res, next) => { const expected = browserUseService.getMcpToken(); const token = readBearerToken(req.headers.authorization) || String(req.headers['x-browser-use-mcp-token'] || ''); if (!token || token !== expected) { - res.status(401).json({ success: false, error: 'Invalid Browser Use MCP token.' }); + res.status(401).json({ success: false, error: 'Invalid Browser MCP token.' }); return; } next(); @@ -104,7 +104,7 @@ router.post('/tools/:toolName', async (req, res) => { result = await browserUseService.agentStopSession(sessionId); break; default: - res.status(404).json({ success: false, error: `Unknown Browser Use MCP tool "${toolName}".` }); + res.status(404).json({ success: false, error: `Unknown Browser MCP tool "${toolName}".` }); return; } @@ -112,7 +112,7 @@ router.post('/tools/:toolName', async (req, res) => { } catch (error) { res.status(400).json({ success: false, - error: error instanceof Error ? error.message : 'Browser Use MCP tool failed.', + error: error instanceof Error ? error.message : 'Browser MCP tool failed.', }); } }); diff --git a/server/modules/browser-use/browser-use.routes.ts b/server/modules/browser-use/browser-use.routes.ts index 6eff6af6..ad428079 100644 --- a/server/modules/browser-use/browser-use.routes.ts +++ b/server/modules/browser-use/browser-use.routes.ts @@ -14,7 +14,7 @@ router.get('/status', async (_req, res) => { } catch (error) { res.status(500).json({ success: false, - error: error instanceof Error ? error.message : 'Failed to load Browser Use status.', + error: error instanceof Error ? error.message : 'Failed to load Browser status.', }); } }); @@ -25,7 +25,7 @@ router.get('/settings', async (_req, res) => { } catch (error) { res.status(500).json({ success: false, - error: error instanceof Error ? error.message : 'Failed to load Browser Use settings.', + error: error instanceof Error ? error.message : 'Failed to load Browser settings.', }); } }); @@ -37,7 +37,7 @@ router.put('/settings', async (req, res) => { } catch (error) { res.status(400).json({ success: false, - error: error instanceof Error ? error.message : 'Failed to save Browser Use settings.', + error: error instanceof Error ? error.message : 'Failed to save Browser settings.', }); } }); @@ -53,7 +53,7 @@ router.post('/runtime/install', async (_req, res) => { } catch (error) { res.status(500).json({ success: false, - error: error instanceof Error ? error.message : 'Failed to install Browser Use runtime.', + error: error instanceof Error ? error.message : 'Failed to install Browser runtime.', }); } }); diff --git a/server/modules/browser-use/browser-use.service.ts b/server/modules/browser-use/browser-use.service.ts index e3837b6b..e521668f 100644 --- a/server/modules/browser-use/browser-use.service.ts +++ b/server/modules/browser-use/browser-use.service.ts @@ -76,8 +76,8 @@ const DEFAULT_SETTINGS: BrowserUseSettings = { }; const AGENT_OWNER_ID = 'agent'; const PROFILE_ROOT = path.join(os.homedir(), '.cloudcli', 'browser-use', 'profiles'); -const MCP_SERVER_NAME = 'cloudcli-browser-use'; -const MCP_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'opencode']; +const MCP_SERVER_NAME = 'cloudcli-browser'; +const LEGACY_MCP_SERVER_NAMES = ['cloudcli-browser-use']; function getRuntime(): BrowserUseRuntime { return IS_PLATFORM ? 'cloud' : 'local'; @@ -95,7 +95,7 @@ function readSettings(): BrowserUseSettings { enabled: parsed.enabled === true, }; } catch (error: any) { - console.warn('[Browser Use] Failed to read settings:', error?.message || error); + console.warn('[Browser] Failed to read settings:', error?.message || error); return DEFAULT_SETTINGS; } } @@ -121,7 +121,7 @@ function getOrCreateMcpToken(): string { function getSetupMessage(settings: BrowserUseSettings, readiness: RuntimeReadiness): string { if (!settings.enabled) { - return 'Browser Use is disabled in settings.'; + return 'Browser is disabled in settings.'; } if (!readiness.playwrightInstalled) { @@ -132,7 +132,7 @@ function getSetupMessage(settings: BrowserUseSettings, readiness: RuntimeReadine return 'Playwright is installed, but Chromium is missing. Install the Chromium runtime to continue.'; } - return readiness.installMessage || 'Browser Use runtime is not ready.'; + return readiness.installMessage || 'Browser runtime is not ready.'; } function getPlaywright(): any | null { @@ -164,6 +164,14 @@ function getMcpApiUrl(): string { return `http://127.0.0.1:${port}/api/browser-use-mcp`; } +async function removeMcpServerFromAllProviders(name: string) { + const results = await providerMcpService.removeMcpServerFromAllProviders({ + name, + scope: 'user', + }); + return results.map((result) => ({ ...result, name })); +} + function normalizeProfileName(profileName?: string | null): string | null { const normalized = String(profileName || '').trim(); if (!normalized) { @@ -259,7 +267,7 @@ function formatInstallError(error: unknown): string { if (message.includes('sudo') && message.includes('password')) { return 'Installing Chromium system dependencies requires administrator privileges. Run `npx playwright install-deps chromium` on the machine where CloudCLI runs, then try again.'; } - return message || 'Failed to install Browser Use runtime.'; + return message || 'Failed to install Browser runtime.'; } async function installRuntime(): Promise<{ success: boolean; message: string }> { @@ -281,7 +289,7 @@ async function installRuntime(): Promise<{ success: boolean; message: string }> lastInstallMessage = 'Installing Chromium runtime...'; await runCommand(npmCommand, ['exec', '--', 'playwright', 'install', 'chromium']); - lastInstallMessage = 'Browser Use runtime installed.'; + lastInstallMessage = 'Browser runtime installed.'; return { success: true, message: lastInstallMessage }; } catch (error) { lastInstallMessage = formatInstallError(error); @@ -418,13 +426,14 @@ export const browserUseService = { installInProgress: readiness.installInProgress, sessionCount: sessions.size, message: available - ? 'Browser Use runtime is available.' + ? 'Browser runtime is available.' : getSetupMessage(settings, readiness), }; }, async registerAgentMcp() { const { command, args } = getMcpCommand(); + await Promise.all(LEGACY_MCP_SERVER_NAMES.map((name) => removeMcpServerFromAllProviders(name))); const results = await providerMcpService.addMcpServerToAllProviders({ name: MCP_SERVER_NAME, scope: 'user', @@ -444,21 +453,9 @@ export const browserUseService = { }, async unregisterAgentMcp() { - const results = await Promise.all(MCP_PROVIDERS.map(async (provider) => { - try { - const result = await providerMcpService.removeProviderMcpServer(provider, { - name: MCP_SERVER_NAME, - scope: 'user', - }); - return { provider, removed: result.removed }; - } catch (error) { - return { - provider, - removed: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } - })); + const results = (await Promise.all( + [MCP_SERVER_NAME, ...LEGACY_MCP_SERVER_NAMES].map((name) => removeMcpServerFromAllProviders(name)), + )).flat(); return { name: MCP_SERVER_NAME, results }; }, @@ -480,7 +477,7 @@ export const browserUseService = { async createAgentSession(options?: { profileName?: string | null }) { const settings = readSettings(); if (!settings.enabled) { - throw new Error('Browser Use agent tools are disabled.'); + throw new Error('Browser agent tools are disabled.'); } await expireStaleSessions(); @@ -507,7 +504,7 @@ export const browserUseService = { const activeOwnerSessions = ownerSessions(AGENT_OWNER_ID).filter((item) => item.status === 'ready'); if (activeOwnerSessions.length >= MAX_SESSIONS_PER_OWNER) { - throw new Error(`Browser Use is limited to ${MAX_SESSIONS_PER_OWNER} active agent sessions.`); + throw new Error(`Browser is limited to ${MAX_SESSIONS_PER_OWNER} active agent sessions.`); } const readiness = getRuntimeReadiness(); @@ -563,7 +560,7 @@ export const browserUseService = { async getAgentSession(sessionId: string) { const settings = readSettings(); if (!settings.enabled) { - throw new Error('Browser Use agent tools are disabled.'); + throw new Error('Browser agent tools are disabled.'); } const session = sessions.get(sessionId); if (!session || session.ownerId !== AGENT_OWNER_ID) { diff --git a/server/modules/browser-use/tests/browser-use.service.test.ts b/server/modules/browser-use/tests/browser-use.service.test.ts index 9494a3e1..b15365f1 100644 --- a/server/modules/browser-use/tests/browser-use.service.test.ts +++ b/server/modules/browser-use/tests/browser-use.service.test.ts @@ -3,7 +3,7 @@ import test from 'node:test'; import { browserUseService } from '@/modules/browser-use/browser-use.service.js'; -test('browser use monitor list starts empty without agent sessions', async () => { +test('browser monitor list starts empty without agent sessions', async () => { const sessions = await browserUseService.listSessions(); assert.deepEqual(sessions, []); diff --git a/server/modules/providers/services/mcp.service.ts b/server/modules/providers/services/mcp.service.ts index da7a5665..6e34e472 100644 --- a/server/modules/providers/services/mcp.service.ts +++ b/server/modules/providers/services/mcp.service.ts @@ -80,4 +80,30 @@ export const providerMcpService = { return results; }, + + /** + * Removes one MCP server from every provider. Mirrors `addMcpServerToAllProviders` + * by iterating the live provider registry, so callers stay in sync with which + * providers exist instead of maintaining their own provider list. + */ + async removeMcpServerFromAllProviders( + input: { name: string; scope?: McpScope; workspacePath?: string }, + ): Promise> { + const results: Array<{ provider: LLMProvider; removed: boolean; error?: string }> = []; + const providers = providerRegistry.listProviders(); + for (const provider of providers) { + try { + const result = await provider.mcp.removeServer(input); + results.push({ provider: provider.id, removed: result.removed }); + } catch (error) { + results.push({ + provider: provider.id, + removed: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + return results; + }, }; diff --git a/src/components/app/AppContent.tsx b/src/components/app/AppContent.tsx index 8445a0b2..6cae890e 100644 --- a/src/components/app/AppContent.tsx +++ b/src/components/app/AppContent.tsx @@ -71,7 +71,6 @@ function AppContentInner() { setActiveTab, setSidebarOpen, setIsInputFocused, - setShowSettings, openSettings, refreshProjectsSilently, registerOptimisticSession, @@ -247,7 +246,7 @@ function AppContentInner() { onSessionEstablished={(targetSessionId, context) => registerOptimisticSession({ sessionId: targetSessionId, ...context }) } - onShowSettings={() => setShowSettings(true)} + onShowSettings={openSettings} externalMessageUpdate={externalMessageUpdate} newSessionTrigger={newSessionTrigger} /> diff --git a/src/components/browser-use/view/BrowserUsePanel.tsx b/src/components/browser-use/view/BrowserUsePanel.tsx index dc326112..b5b5f2a9 100644 --- a/src/components/browser-use/view/BrowserUsePanel.tsx +++ b/src/components/browser-use/view/BrowserUsePanel.tsx @@ -17,6 +17,7 @@ import { import { cn } from '../../../lib/utils'; import { Badge, Button } from '../../../shared/view/ui'; import { authenticatedFetch } from '../../../utils/api'; +import type { SettingsMainTab } from '../../settings/types/types'; type BrowserUseStatus = { enabled: boolean; @@ -53,7 +54,7 @@ type BrowserUseSession = { type BrowserUsePanelProps = { isVisible: boolean; - onShowSettings?: () => void; + onShowSettings?: (tab?: SettingsMainTab) => void; }; async function readJson(response: Response): Promise { @@ -119,8 +120,8 @@ function getStatusDot(status: BrowserUseSession['status']): string { } const PROMPTS = [ - 'Use Browser Use to inspect the checkout flow and report any broken UI states.', - 'Open with Browser Use, interact with the page, and summarize what changed after each step.', + 'Use Browser to inspect the checkout flow and report any broken UI states.', + 'Open with Browser, interact with the page, and summarize what changed after each step.', ]; export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUsePanelProps) { @@ -174,7 +175,7 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs )); setError(null); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load Browser Use'); + setError(err instanceof Error ? err.message : 'Failed to load Browser'); } finally { setIsRefreshing(false); } @@ -192,7 +193,7 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs await action(); await refresh(); } catch (err) { - setError(err instanceof Error ? err.message : 'Browser Use action failed'); + setError(err instanceof Error ? err.message : 'Browser action failed'); } finally { setIsBusy(false); } @@ -265,12 +266,12 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs
- {status?.enabled ? 'No browser sessions yet' : 'Browser Use is disabled'} + {status?.enabled ? 'No browser sessions yet' : 'Browser is disabled'}

{status?.enabled - ? 'Agent browser sessions appear here while an AI task is using Browser Use.' - : 'Enable Browser Use in settings to let agents open monitored browser sessions.'} + ? 'Agent browser sessions appear here while an AI task is using Browser.' + : 'Enable Browser in settings to let agents open monitored browser sessions.'}

@@ -345,7 +346,7 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs
-

Browser Use

+

Browser

{runtimeLabel} @@ -358,9 +359,9 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs variant="ghost" size="sm" className="h-7 w-7 p-0" - onClick={onShowSettings} - title="Open Browser Use settings" - aria-label="Open Browser Use settings" + onClick={() => onShowSettings('browser')} + title="Open Browser settings" + aria-label="Open Browser settings" > diff --git a/src/components/main-content/types/types.ts b/src/components/main-content/types/types.ts index 6ae16ec8..6fd3ac7d 100644 --- a/src/components/main-content/types/types.ts +++ b/src/components/main-content/types/types.ts @@ -7,6 +7,7 @@ import type { SessionActivityMap, } from '../../../hooks/useSessionProtection'; import type { SessionEstablishedContext, SessionNavigationOptions } from '../../chat/types/types'; +import type { SettingsMainTab } from '../../settings/types/types'; export type TaskMasterTask = { id: string | number; @@ -53,7 +54,7 @@ export type MainContentProps = { processingSessions: SessionActivityMap; onNavigateToSession: (targetSessionId: string, options?: SessionNavigationOptions) => void; onSessionEstablished: (sessionId: string, context: SessionEstablishedContext) => void; - onShowSettings: () => void; + onShowSettings: (tab?: SettingsMainTab) => void; externalMessageUpdate: number; newSessionTrigger: number; }; diff --git a/src/components/main-content/view/subcomponents/MainContentTitle.tsx b/src/components/main-content/view/subcomponents/MainContentTitle.tsx index 78f0dfc0..b05da336 100644 --- a/src/components/main-content/view/subcomponents/MainContentTitle.tsx +++ b/src/components/main-content/view/subcomponents/MainContentTitle.tsx @@ -29,7 +29,7 @@ function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: (key: st } if (activeTab === 'browser') { - return 'Browser Use'; + return 'Browser'; } return 'Project'; diff --git a/src/components/mcp/view/McpServers.tsx b/src/components/mcp/view/McpServers.tsx index 8ec9d03e..85ec18a4 100644 --- a/src/components/mcp/view/McpServers.tsx +++ b/src/components/mcp/view/McpServers.tsx @@ -52,6 +52,11 @@ const getServerKey = (server: ProviderMcpServer): string => ( `${server.provider}:${server.scope}:${server.workspacePath || 'global'}:${server.name}` ); +// Servers prefixed with `cloudcli-` are written and removed automatically by a +// CloudCLI feature toggle (e.g. the Browser tab), not added by the user. They are +// shown read-only so users don't edit/delete them out of sync with the feature. +const isManagedServer = (server: ProviderMcpServer): boolean => server.name.startsWith('cloudcli-'); + function ConfigLine({ label, children }: { label: string; children: string }) { if (!children) { return null; @@ -195,6 +200,12 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer {server.projectDisplayName} )} + {isManagedServer(server) && ( + + + {t('mcpServers.managed.badge', { defaultValue: 'Managed' })} + + )}
@@ -210,29 +221,38 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer {server.envVars && server.envVars.length > 0 && ( {server.envVars.join(', ')} )} + {isManagedServer(server) && ( +
+ {t('mcpServers.managed.hint', { + defaultValue: 'Managed by CloudCLI — control it from the feature\'s settings toggle.', + })} +
+ )}
-
- - -
+ {!isManagedServer(server) && ( +
+ + +
+ )} ))} diff --git a/src/components/settings/constants/constants.ts b/src/components/settings/constants/constants.ts index 37fa9df3..8e5bfebe 100644 --- a/src/components/settings/constants/constants.ts +++ b/src/components/settings/constants/constants.ts @@ -33,7 +33,7 @@ export const SETTINGS_MAIN_TABS: SettingsMainTabMeta[] = [ { id: 'git', label: 'Git', keywords: 'git github commits', icon: GitBranch }, { id: 'api', label: 'API Tokens', keywords: 'api tokens auth keys', icon: KeyRound }, { id: 'tasks', label: 'Tasks', keywords: 'tasks taskmaster', icon: ListChecks }, - { id: 'browser', label: 'Browser Use', keywords: 'browser use playwright chromium automation', icon: MonitorPlay }, + { id: 'browser', label: 'Browser', keywords: 'browser playwright chromium automation', icon: MonitorPlay }, { id: 'notifications', label: 'Notifications', keywords: 'notifications alerts push', icon: Bell }, { id: 'plugins', label: 'Plugins', keywords: 'plugins extensions integrations', icon: Plug }, { id: 'about', label: 'About', keywords: 'about version info', icon: Info }, diff --git a/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx b/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx index 474b4d36..9961c31f 100644 --- a/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx +++ b/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx @@ -52,7 +52,7 @@ export default function BrowserUseSettingsTab() { useEffect(() => { setIsLoading(true); void loadState() - .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load Browser Use settings')) + .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load Browser settings')) .finally(() => setIsLoading(false)); }, [loadState]); @@ -69,7 +69,7 @@ export default function BrowserUseSettingsTab() { window.dispatchEvent(new Event('browserUseSettingsChanged')); await loadState(); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to save Browser Use settings'); + setError(err instanceof Error ? err.message : 'Failed to save Browser settings'); } finally { setIsSaving(false); } @@ -94,18 +94,18 @@ export default function BrowserUseSettingsTab() { return (
void updateSettings({ enabled: value })} - ariaLabel="Enable Browser Use" + ariaLabel="Enable Browser" disabled={isLoading || isSaving} /> @@ -128,7 +128,7 @@ export default function BrowserUseSettingsTab() {
Browser runtime required

- {status?.message || 'Install the browser runtime before agents can create Browser Use sessions.'} + {status?.message || 'Install the browser runtime before agents can create Browser sessions.'}

diff --git a/src/components/sidebar/view/subcomponents/SidebarFooter.tsx b/src/components/sidebar/view/subcomponents/SidebarFooter.tsx index b024be30..ef1c7178 100644 --- a/src/components/sidebar/view/subcomponents/SidebarFooter.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarFooter.tsx @@ -69,7 +69,7 @@ export default function SidebarFooter({ onClick={onShowVersionModal} >
- +
@@ -145,12 +145,12 @@ export default function SidebarFooter({ href={GITHUB_ISSUES_URL} target="_blank" rel="noopener noreferrer" - className="flex h-12 w-full items-center gap-3.5 rounded-xl bg-muted/40 px-4 transition-all hover:bg-muted/60 active:scale-[0.98]" + className="flex h-10 w-full items-center gap-3 rounded-xl bg-muted/40 px-3.5 transition-all hover:bg-muted/60 active:scale-[0.98]" > -
- +
+
- {t('actions.reportIssue')} + {t('actions.reportIssue')}
@@ -160,25 +160,25 @@ export default function SidebarFooter({ href={DISCORD_INVITE_URL} target="_blank" rel="noopener noreferrer" - className="flex h-12 w-full items-center gap-3.5 rounded-xl bg-muted/40 px-4 transition-all hover:bg-muted/60 active:scale-[0.98]" + className="flex h-10 w-full items-center gap-3 rounded-xl bg-muted/40 px-3.5 transition-all hover:bg-muted/60 active:scale-[0.98]" > -
- +
+
- {t('actions.joinCommunity')} + {t('actions.joinCommunity')}
{/* Mobile settings */}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index bae8db89..eddd8f84 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -94,7 +94,7 @@ "git": "Git", "apiTokens": "API & Tokens", "tasks": "Tasks", - "browser": "Browser Use", + "browser": "Browser", "notifications": "Notifications", "plugins": "Plugins", "about": "About" @@ -451,6 +451,10 @@ "edit": "Edit server", "delete": "Delete server" }, + "managed": { + "badge": "Managed", + "hint": "Managed by CloudCLI — control it from the feature's settings toggle." + }, "help": { "title": "About Codex MCP", "description": "Codex supports stdio-based MCP servers. You can add servers that extend Codex's capabilities with additional tools and resources."