diff --git a/electron/launcher/launcher.js b/electron/launcher/launcher.js index 11bf6226..ed5d72c4 100644 --- a/electron/launcher/launcher.js +++ b/electron/launcher/launcher.js @@ -739,12 +739,9 @@ window.__MOCK_STATE__ = { } function localPane(state) { - var computerStatus = CC.computerUseStatus(state); return '

Local servers

Manage Local CloudCLI on this machine. No account required.

' + '
Local server
' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '
' + - '
' + - '
Computer Use
' + CC.esc(computerStatus.detail) + '
' + CC.esc(computerStatus.label) + '
' + - '
'; + '
'; } function envRow(environment) { diff --git a/server/modules/computer-use/computer-use.service.ts b/server/modules/computer-use/computer-use.service.ts index 4eb71997..86094e6e 100644 --- a/server/modules/computer-use/computer-use.service.ts +++ b/server/modules/computer-use/computer-use.service.ts @@ -125,12 +125,12 @@ function getOrCreateMcpToken(): string { } function getSetupMessage(settings: ComputerUseSettings, readiness: RuntimeReadiness): string { - if (getRuntime() === 'cloud') { - return 'Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.'; - } if (!settings.enabled) { return 'Computer Use is disabled in settings.'; } + if (getRuntime() === 'cloud') { + return 'Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.'; + } if (!readiness.nutInstalled || !readiness.screenshotInstalled) { return 'Install the desktop control runtime to capture the screen and drive the mouse and keyboard.'; } @@ -416,22 +416,25 @@ function assertReady(session: ComputerUseSession): void { } } -/** - * Whether agent tools may operate right now. Cloud mode depends purely on a - * connected desktop agent; local mode depends on the single feature setting. - */ function agentToolsAvailable(): boolean { + const settings = readSettings(); + if (!settings.enabled) { + return false; + } if (getRuntime() === 'cloud') { return desktopAgentRelay.isConnected(); } - const settings = readSettings(); - return settings.enabled; + return true; } function assertAgentToolsAvailable(): void { if (agentToolsAvailable()) { return; } + const settings = readSettings(); + if (!settings.enabled) { + throw new Error('Computer Use agent tools are disabled.'); + } throw new Error( getRuntime() === 'cloud' ? 'No desktop is linked. Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.' @@ -439,6 +442,16 @@ function assertAgentToolsAvailable(): void { ); } +function stopSessions(lastAction: string, message: string): void { + for (const session of sessions.values()) { + session.status = 'stopped'; + session.agentAccessEnabled = false; + session.updatedAt = new Date().toISOString(); + session.lastAction = lastAction; + session.message = message; + } +} + export const computerUseService = { async getSettings() { return readSettings(); @@ -450,8 +463,9 @@ export const computerUseService = { const next = writeSettings({ enabled }); if (next.enabled) { await this.registerAgentMcp(); - } else if (current.enabled) { + } else { await this.unregisterAgentMcp(); + stopSessions('settings:disabled', 'Computer Use was disabled in settings.'); } return next; }, @@ -461,16 +475,16 @@ export const computerUseService = { const readiness = getRuntimeReadiness(); const isCloud = getRuntime() === 'cloud'; const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled; - // Cloud availability is purely a function of a connected desktop agent; the - // hosted server has no screen of its own. Local availability needs the - // in-process nut-js runtime installed and the feature enabled. + // Cloud mode still respects the saved feature setting. When enabled, cloud + // availability comes from a linked desktop agent because the hosted server + // has no screen of its own. const desktopAgentConnected = desktopAgentRelay.isConnected(); - const available = isCloud + const available = settings.enabled && (isCloud ? desktopAgentConnected - : settings.enabled && runtimeReady; + : runtimeReady); return { - enabled: isCloud ? true : settings.enabled, + enabled: settings.enabled, runtime: getRuntime(), available, desktopAgentConnected, @@ -571,9 +585,9 @@ export const computerUseService = { const readiness = getRuntimeReadiness(); const isCloud = getRuntime() === 'cloud'; const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled; - const ready = isCloud + const ready = settings.enabled && (isCloud ? desktopAgentRelay.isConnected() - : settings.enabled && runtimeReady; + : runtimeReady); if (!ready) { session.message = getSetupMessage(settings, readiness); @@ -856,14 +870,15 @@ export const computerUseService = { /** * Cloud only: when a desktop agent links to this hosted environment, expose - * the computer_* MCP tools to every provider so the running agent can use - * them. Mirrors `registerAgentMcp` but is driven by relay connectivity rather - * than a settings toggle. + * the computer_* MCP tools only if the user enabled Computer Use in settings. */ async onDesktopAgentConnected() { if (getRuntime() !== 'cloud') { return; } + if (!readSettings().enabled) { + return; + } try { await this.registerAgentMcp(); } catch (error) { @@ -888,13 +903,7 @@ export const computerUseService = { }, async stopAllSessions() { - for (const session of sessions.values()) { - session.status = 'stopped'; - session.agentAccessEnabled = false; - session.updatedAt = new Date().toISOString(); - session.lastAction = 'shutdown'; - session.message = 'Computer Use session stopped during server shutdown.'; - } + stopSessions('shutdown', 'Computer Use session stopped during server shutdown.'); }, }; diff --git a/src/components/settings/view/tabs/computer-use-settings/ComputerUseSettingsTab.tsx b/src/components/settings/view/tabs/computer-use-settings/ComputerUseSettingsTab.tsx index 4d298cde..798eaa3a 100644 --- a/src/components/settings/view/tabs/computer-use-settings/ComputerUseSettingsTab.tsx +++ b/src/components/settings/view/tabs/computer-use-settings/ComputerUseSettingsTab.tsx @@ -130,7 +130,21 @@ export default function ComputerUseSettingsTab() { )} - {isCloud ? ( + + void updateSettings({ enabled: value })} + ariaLabel="Enable Computer Use" + disabled={isLoading || isSaving} + /> + + + {isCloud && ( - ) : ( - - void updateSettings({ enabled: value })} - ariaLabel="Enable Computer Use" - disabled={isLoading || isSaving} - /> - )} {(needsRuntime || isCloud || error) && (