From ffc0cd7501236682381214694d7ddd9b5efbe5f3 Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Wed, 17 Jun 2026 20:04:44 +0000 Subject: [PATCH] Improve Browser settings load and managed MCP display --- .../browser-use/browser-use.service.ts | 32 +++- src/components/mcp/view/McpServers.tsx | 152 ++++++++++-------- .../BrowserUseSettingsTab.tsx | 73 ++++++--- src/i18n/locales/en/settings.json | 2 +- 4 files changed, 159 insertions(+), 100 deletions(-) diff --git a/server/modules/browser-use/browser-use.service.ts b/server/modules/browser-use/browser-use.service.ts index e521668f..280ff730 100644 --- a/server/modules/browser-use/browser-use.service.ts +++ b/server/modules/browser-use/browser-use.service.ts @@ -66,10 +66,13 @@ type RuntimeReadiness = { installMessage: string | null; }; +type RuntimeProbe = Omit; + const sessions = new Map(); const handles = new Map(); let installPromise: Promise<{ success: boolean; message: string }> | null = null; let lastInstallMessage: string | null = null; +let runtimeProbeCache: { value: RuntimeProbe; updatedAt: number } | null = null; const DEFAULT_SETTINGS: BrowserUseSettings = { enabled: false, @@ -78,6 +81,7 @@ const AGENT_OWNER_ID = 'agent'; const PROFILE_ROOT = path.join(os.homedir(), '.cloudcli', 'browser-use', 'profiles'); const MCP_SERVER_NAME = 'cloudcli-browser'; const LEGACY_MCP_SERVER_NAMES = ['cloudcli-browser-use']; +const RUNTIME_READINESS_CACHE_TTL_MS = 30_000; function getRuntime(): BrowserUseRuntime { return IS_PLATFORM ? 'cloud' : 'local'; @@ -190,15 +194,13 @@ function getProfilePath(profileName: string): string { return path.join(PROFILE_ROOT, safeName); } -function getRuntimeReadiness(): RuntimeReadiness { +function probeRuntime(): RuntimeProbe { const playwright = getPlaywright(); - const readiness: RuntimeReadiness = { + const readiness: RuntimeProbe = { playwright, playwrightInstalled: Boolean(playwright), chromiumInstalled: false, chromiumExecutablePath: null, - installInProgress: Boolean(installPromise), - installMessage: lastInstallMessage, }; if (!playwright) { @@ -216,6 +218,26 @@ function getRuntimeReadiness(): RuntimeReadiness { return readiness; } +function getRuntimeReadiness(options: { force?: boolean } = {}): RuntimeReadiness { + const now = Date.now(); + const cachedProbe = runtimeProbeCache; + const canUseCache = !options.force + && !installPromise + && cachedProbe + && now - cachedProbe.updatedAt < RUNTIME_READINESS_CACHE_TTL_MS; + const probe = canUseCache ? cachedProbe.value : probeRuntime(); + + if (!canUseCache && !installPromise) { + runtimeProbeCache = { value: probe, updatedAt: now }; + } + + return { + ...probe, + installInProgress: Boolean(installPromise), + installMessage: lastInstallMessage, + }; +} + const INSTALL_COMMAND_TIMEOUT_MS = Number.parseInt( process.env.CLOUDCLI_BROWSER_USE_INSTALL_TIMEOUT_MS || String(10 * 60 * 1000), 10, @@ -276,6 +298,7 @@ async function installRuntime(): Promise<{ success: boolean; message: string }> } const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + runtimeProbeCache = null; installPromise = (async () => { try { lastInstallMessage = 'Installing Playwright package...'; @@ -301,6 +324,7 @@ async function installRuntime(): Promise<{ success: boolean; message: string }> return await installPromise; } finally { installPromise = null; + runtimeProbeCache = null; } } diff --git a/src/components/mcp/view/McpServers.tsx b/src/components/mcp/view/McpServers.tsx index 85ec18a4..a77117fe 100644 --- a/src/components/mcp/view/McpServers.tsx +++ b/src/components/mcp/view/McpServers.tsx @@ -182,80 +182,92 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer
Loading MCP servers...
)} - {servers.map((server) => ( -
-
-
-
- {getTransportIcon(server.transport)} - {server.name} - - {server.transport || 'stdio'} - - - {getScopeLabel(server.scope)} - - {server.projectDisplayName && ( - - {server.projectDisplayName} - - )} - {isManagedServer(server) && ( - - - {t('mcpServers.managed.badge', { defaultValue: 'Managed' })} - - )} + {servers.map((server) => { + const managed = isManagedServer(server); + + return ( +
+
+
+
+ {!managed && getTransportIcon(server.transport)} + {server.name} + {!managed && ( + <> + + {server.transport || 'stdio'} + + + {getScopeLabel(server.scope)} + + {server.projectDisplayName && ( + + {server.projectDisplayName} + + )} + + )} + {managed && ( + + + {t('mcpServers.managed.badge', { defaultValue: 'Managed' })} + + )} +
+ +
+ {!managed && ( + <> + {server.command || ''} + {server.url || ''} + {(server.args || []).join(' ')} + {server.cwd || ''} + {server.env && Object.keys(server.env).length > 0 && ( + + {Object.entries(server.env).map(([key, value]) => `${key}=${maskSecret(value)}`).join(', ')} + + )} + {server.envVars && server.envVars.length > 0 && ( + {server.envVars.join(', ')} + )} + + )} + {managed && ( +
+ {t('mcpServers.managed.hint', { + defaultValue: 'Managed by CloudCLI.', + })} +
+ )} +
-
- {server.command || ''} - {server.url || ''} - {(server.args || []).join(' ')} - {server.cwd || ''} - {server.env && Object.keys(server.env).length > 0 && ( - - {Object.entries(server.env).map(([key, value]) => `${key}=${maskSecret(value)}`).join(', ')} - - )} - {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.', - })} -
- )} -
+ {!managed && ( +
+ + +
+ )}
- - {!isManagedServer(server) && ( -
- - -
- )}
-
- ))} + ); + })} {!isLoading && !isLoadingProjectScopes && servers.length === 0 && (
{t('mcpServers.empty')}
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 9961c31f..da94ffe2 100644 --- a/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx +++ b/src/components/settings/view/tabs/browser-use-settings/BrowserUseSettingsTab.tsx @@ -30,31 +30,39 @@ async function readJson(response: Response): Promise { } export default function BrowserUseSettingsTab() { - const [settings, setSettings] = useState({ enabled: false }); + const [settings, setSettings] = useState(null); const [status, setStatus] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [isSettingsLoading, setIsSettingsLoading] = useState(true); + const [isStatusLoading, setIsStatusLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [error, setError] = useState(null); - const loadState = useCallback(async () => { - setError(null); - const [settingsResponse, statusResponse] = await Promise.all([ - authenticatedFetch('/api/browser-use/settings'), - authenticatedFetch('/api/browser-use/status'), - ]); + const loadSettings = useCallback(async () => { + const settingsResponse = await authenticatedFetch('/api/browser-use/settings'); const settingsData = await readJson<{ data: { settings: BrowserUseSettings } }>(settingsResponse); - const statusData = await readJson<{ data: BrowserUseStatus }>(statusResponse); setSettings(settingsData.data.settings); + }, []); + + const loadStatus = useCallback(async () => { + const statusResponse = await authenticatedFetch('/api/browser-use/status'); + const statusData = await readJson<{ data: BrowserUseStatus }>(statusResponse); setStatus(statusData.data); }, []); useEffect(() => { - setIsLoading(true); - void loadState() + setError(null); + setIsSettingsLoading(true); + setIsStatusLoading(true); + + void loadSettings() .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load Browser settings')) - .finally(() => setIsLoading(false)); - }, [loadState]); + .finally(() => setIsSettingsLoading(false)); + + void loadStatus() + .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load Browser status')) + .finally(() => setIsStatusLoading(false)); + }, [loadSettings, loadStatus]); const updateSettings = async (nextSettings: Partial) => { setIsSaving(true); @@ -67,10 +75,12 @@ export default function BrowserUseSettingsTab() { const data = await readJson<{ data: { settings: BrowserUseSettings } }>(response); setSettings(data.data.settings); window.dispatchEvent(new Event('browserUseSettingsChanged')); - await loadState(); + setIsStatusLoading(true); + await loadStatus(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save Browser settings'); } finally { + setIsStatusLoading(false); setIsSaving(false); } }; @@ -81,15 +91,24 @@ export default function BrowserUseSettingsTab() { try { const response = await authenticatedFetch('/api/browser-use/runtime/install', { method: 'POST' }); await readJson(response); - await loadState(); + setIsStatusLoading(true); + await loadStatus(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to install browser runtime'); } finally { + setIsStatusLoading(false); setIsInstalling(false); } }; - const needsBrowserBinaries = Boolean(settings.enabled && status && (!status.playwrightInstalled || !status.chromiumInstalled)); + const browserEnabled = settings?.enabled === true; + const needsBrowserBinaries = Boolean(browserEnabled && status && (!status.playwrightInstalled || !status.chromiumInstalled)); + const runtimeLabel = (installed?: boolean) => { + if (isStatusLoading && !status) { + return 'checking...'; + } + return installed ? 'installed' : 'missing'; + }; return (
@@ -102,24 +121,28 @@ export default function BrowserUseSettingsTab() { label="Enable Browser" description="Registers Browser for supported agents. Agents can create browser sessions; you can watch, stop, and delete them." > - void updateSettings({ enabled: value })} - ariaLabel="Enable Browser" - disabled={isLoading || isSaving} - /> + {isSettingsLoading && !settings ? ( + + ) : ( + void updateSettings({ enabled: value })} + ariaLabel="Enable Browser" + disabled={isSaving} + /> + )}
- Playwright: {status?.playwrightInstalled ? 'installed' : 'missing'} + Playwright: {runtimeLabel(status?.playwrightInstalled)} - Chromium: {status?.chromiumInstalled ? 'installed' : 'missing'} + Chromium: {runtimeLabel(status?.chromiumInstalled)} - Status: {status?.available ? 'ready' : settings.enabled ? 'setup required' : 'disabled'} + Status: {isStatusLoading && !status ? 'checking...' : status?.available ? 'ready' : browserEnabled ? 'setup required' : 'disabled'}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index eddd8f84..c9513adf 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -453,7 +453,7 @@ }, "managed": { "badge": "Managed", - "hint": "Managed by CloudCLI — control it from the feature's settings toggle." + "hint": "Managed by CloudCLI." }, "help": { "title": "About Codex MCP",