fix: respect cloud computer use setting

This commit is contained in:
Simos Mikelatos
2026-06-19 15:02:07 +00:00
parent 5ef40be2d3
commit bb323fc566
3 changed files with 53 additions and 45 deletions

View File

@@ -739,12 +739,9 @@ window.__MOCK_STATE__ = {
} }
function localPane(state) { function localPane(state) {
var computerStatus = CC.computerUseStatus(state);
return '<div class="pane-h"><div><h2 class="pane-title">Local servers</h2><p class="pane-sub">Manage Local CloudCLI on this machine. No account required.</p></div></div>' + return '<div class="pane-h"><div><h2 class="pane-title">Local servers</h2><p class="pane-sub">Manage Local CloudCLI on this machine. No account required.</p></div></div>' +
'<div class="card"><div class="card-head"><div><div class="card-t">Local server</div><div class="card-sub mono">' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '</div></div><div class="card-tools"><span class="dot" style="background:' + (state.localServerRunning ? 'var(--ok)' : 'var(--tx3)') + '"></span><button class="icon-btn" data-cc-action="local-settings-toggle" title="Local settings">' + CC.icon('gear', 16) + '</button></div></div>' + '<div class="card"><div class="card-head"><div><div class="card-t">Local server</div><div class="card-sub mono">' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '</div></div><div class="card-tools"><span class="dot" style="background:' + (state.localServerRunning ? 'var(--ok)' : 'var(--tx3)') + '"></span><button class="icon-btn" data-cc-action="local-settings-toggle" title="Local settings">' + CC.icon('gear', 16) + '</button></div></div>' +
'<div class="card-actions"><button class="btn pri" data-cc-action="local">' + CC.icon('play', 15) + 'Open Local CloudCLI</button><button class="btn" data-cc-action="open-web">' + CC.icon('arrow', 14) + 'Open in browser</button><button class="btn" data-cc-action="copy-web">' + CC.icon('copy', 14) + 'Copy URL</button></div></div>' + '<div class="card-actions"><button class="btn pri" data-cc-action="local">' + CC.icon('play', 15) + 'Open Local CloudCLI</button><button class="btn" data-cc-action="open-web">' + CC.icon('arrow', 14) + 'Open in browser</button><button class="btn" data-cc-action="copy-web">' + CC.icon('copy', 14) + 'Copy URL</button></div></div>';
'<div class="card"><div class="card-head"><div><div class="card-t">Computer Use</div><div class="card-sub">' + CC.esc(computerStatus.detail) + '</div></div><div class="card-tools"><span class="badge ' + CC.esc(computerStatus.tone) + '">' + CC.esc(computerStatus.label) + '</span><button class="icon-btn" data-cc-action="computer-settings-toggle" title="Computer Use settings">' + CC.icon('monitor', 16) + '</button></div></div>' +
'<div class="card-actions"><button class="btn" data-cc-action="refresh-environments">' + CC.icon('refresh', 14) + 'Refresh / relink</button><button class="btn" data-cc-action="computer-settings-toggle">' + CC.icon('settings', 14) + 'Open settings</button></div></div>';
} }
function envRow(environment) { function envRow(environment) {

View File

@@ -125,12 +125,12 @@ function getOrCreateMcpToken(): string {
} }
function getSetupMessage(settings: ComputerUseSettings, readiness: RuntimeReadiness): 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) { if (!settings.enabled) {
return 'Computer Use is disabled in settings.'; 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) { if (!readiness.nutInstalled || !readiness.screenshotInstalled) {
return 'Install the desktop control runtime to capture the screen and drive the mouse and keyboard.'; 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 { function agentToolsAvailable(): boolean {
const settings = readSettings();
if (!settings.enabled) {
return false;
}
if (getRuntime() === 'cloud') { if (getRuntime() === 'cloud') {
return desktopAgentRelay.isConnected(); return desktopAgentRelay.isConnected();
} }
const settings = readSettings(); return true;
return settings.enabled;
} }
function assertAgentToolsAvailable(): void { function assertAgentToolsAvailable(): void {
if (agentToolsAvailable()) { if (agentToolsAvailable()) {
return; return;
} }
const settings = readSettings();
if (!settings.enabled) {
throw new Error('Computer Use agent tools are disabled.');
}
throw new Error( throw new Error(
getRuntime() === 'cloud' getRuntime() === 'cloud'
? 'No desktop is linked. Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.' ? '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 = { export const computerUseService = {
async getSettings() { async getSettings() {
return readSettings(); return readSettings();
@@ -450,8 +463,9 @@ export const computerUseService = {
const next = writeSettings({ enabled }); const next = writeSettings({ enabled });
if (next.enabled) { if (next.enabled) {
await this.registerAgentMcp(); await this.registerAgentMcp();
} else if (current.enabled) { } else {
await this.unregisterAgentMcp(); await this.unregisterAgentMcp();
stopSessions('settings:disabled', 'Computer Use was disabled in settings.');
} }
return next; return next;
}, },
@@ -461,16 +475,16 @@ export const computerUseService = {
const readiness = getRuntimeReadiness(); const readiness = getRuntimeReadiness();
const isCloud = getRuntime() === 'cloud'; const isCloud = getRuntime() === 'cloud';
const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled; const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled;
// Cloud availability is purely a function of a connected desktop agent; the // Cloud mode still respects the saved feature setting. When enabled, cloud
// hosted server has no screen of its own. Local availability needs the // availability comes from a linked desktop agent because the hosted server
// in-process nut-js runtime installed and the feature enabled. // has no screen of its own.
const desktopAgentConnected = desktopAgentRelay.isConnected(); const desktopAgentConnected = desktopAgentRelay.isConnected();
const available = isCloud const available = settings.enabled && (isCloud
? desktopAgentConnected ? desktopAgentConnected
: settings.enabled && runtimeReady; : runtimeReady);
return { return {
enabled: isCloud ? true : settings.enabled, enabled: settings.enabled,
runtime: getRuntime(), runtime: getRuntime(),
available, available,
desktopAgentConnected, desktopAgentConnected,
@@ -571,9 +585,9 @@ export const computerUseService = {
const readiness = getRuntimeReadiness(); const readiness = getRuntimeReadiness();
const isCloud = getRuntime() === 'cloud'; const isCloud = getRuntime() === 'cloud';
const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled; const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled;
const ready = isCloud const ready = settings.enabled && (isCloud
? desktopAgentRelay.isConnected() ? desktopAgentRelay.isConnected()
: settings.enabled && runtimeReady; : runtimeReady);
if (!ready) { if (!ready) {
session.message = getSetupMessage(settings, readiness); session.message = getSetupMessage(settings, readiness);
@@ -856,14 +870,15 @@ export const computerUseService = {
/** /**
* Cloud only: when a desktop agent links to this hosted environment, expose * 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 * the computer_* MCP tools only if the user enabled Computer Use in settings.
* them. Mirrors `registerAgentMcp` but is driven by relay connectivity rather
* than a settings toggle.
*/ */
async onDesktopAgentConnected() { async onDesktopAgentConnected() {
if (getRuntime() !== 'cloud') { if (getRuntime() !== 'cloud') {
return; return;
} }
if (!readSettings().enabled) {
return;
}
try { try {
await this.registerAgentMcp(); await this.registerAgentMcp();
} catch (error) { } catch (error) {
@@ -888,13 +903,7 @@ export const computerUseService = {
}, },
async stopAllSessions() { async stopAllSessions() {
for (const session of sessions.values()) { stopSessions('shutdown', 'Computer Use session stopped during server shutdown.');
session.status = 'stopped';
session.agentAccessEnabled = false;
session.updatedAt = new Date().toISOString();
session.lastAction = 'shutdown';
session.message = 'Computer Use session stopped during server shutdown.';
}
}, },
}; };

View File

@@ -130,7 +130,21 @@ export default function ComputerUseSettingsTab() {
)} )}
</div> </div>
{isCloud ? ( <SettingsRow
label="Enable Computer Use"
description={isCloud
? 'Registers Computer Use MCP servers for supported agents and allows cloud agents to request guarded access to a linked desktop.'
: 'Registers Computer Use for supported agents and allows CloudCLI to create guarded desktop control sessions on this machine.'}
>
<SettingsToggle
checked={settings.enabled}
onChange={(value) => void updateSettings({ enabled: value })}
ariaLabel="Enable Computer Use"
disabled={isLoading || isSaving}
/>
</SettingsRow>
{isCloud && (
<SettingsRow <SettingsRow
label="Cloud desktop access" label="Cloud desktop access"
description={status?.desktopAgentConnected description={status?.desktopAgentConnected
@@ -160,18 +174,6 @@ export default function ComputerUseSettingsTab() {
</div> </div>
</div> </div>
</SettingsRow> </SettingsRow>
) : (
<SettingsRow
label="Enable Computer Use"
description="Registers Computer Use for supported agents and allows CloudCLI to create guarded desktop control sessions on this machine."
>
<SettingsToggle
checked={settings.enabled}
onChange={(value) => void updateSettings({ enabled: value })}
ariaLabel="Enable Computer Use"
disabled={isLoading || isSaving}
/>
</SettingsRow>
)} )}
{(needsRuntime || isCloud || error) && ( {(needsRuntime || isCloud || error) && (