mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-30 17:12:58 +08:00
chore: remove computer use
This commit is contained in:
@@ -1,290 +0,0 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const IPC_PREFIX = '@@CUAGENT@@';
|
||||
const TARGET_STATUS_TIMEOUT_MS = 5000;
|
||||
|
||||
function getDesktopPath() {
|
||||
const currentPath = process.env.PATH || '';
|
||||
const commonPaths = process.platform === 'win32'
|
||||
? []
|
||||
: ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
||||
return [...commonPaths, currentPath].filter(Boolean).join(path.delimiter);
|
||||
}
|
||||
|
||||
function getNodeRuntime(isPackaged) {
|
||||
if (isPackaged && process.versions.electron) {
|
||||
return { command: process.execPath, env: { ELECTRON_RUN_AS_NODE: '1' } };
|
||||
}
|
||||
if (process.env.npm_node_execpath) {
|
||||
return { command: process.env.npm_node_execpath, env: {} };
|
||||
}
|
||||
return { command: 'node', env: {} };
|
||||
}
|
||||
|
||||
function toAgentWsUrl(httpUrl) {
|
||||
try {
|
||||
const parsed = new URL(httpUrl);
|
||||
parsed.protocol = parsed.protocol === 'http:' ? 'ws:' : 'wss:';
|
||||
parsed.pathname = '/desktop-agent';
|
||||
parsed.search = '';
|
||||
parsed.hash = '';
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function isComputerUseEnabledTarget(httpUrl, apiKey) {
|
||||
let statusUrl;
|
||||
try {
|
||||
statusUrl = new URL('/api/computer-use/status', httpUrl).toString();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), TARGET_STATUS_TIMEOUT_MS);
|
||||
try {
|
||||
const response = await fetch(statusUrl, {
|
||||
signal: controller.signal,
|
||||
headers: apiKey ? { 'X-API-Key': apiKey } : undefined,
|
||||
});
|
||||
const body = await response.json().catch(() => null);
|
||||
return response.ok && body?.success !== false && body?.data?.enabled === true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async function filterEnabledComputerUseTargets(targets, apiKey) {
|
||||
const checks = await Promise.all(targets.map(async (target) => ({
|
||||
target,
|
||||
enabled: await isComputerUseEnabledTarget(target, apiKey),
|
||||
})));
|
||||
return checks.filter((item) => item.enabled).map((item) => item.target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a Computer Use desktop agent connected to running cloud environments
|
||||
* while desktop access is enabled.
|
||||
*/
|
||||
export class ComputerAgentController {
|
||||
constructor({ appRoot, settingsPath, isPackaged = false, getRunningEnvironmentUrls, getApiKey, promptConsent, onChange }) {
|
||||
this.appRoot = appRoot;
|
||||
this.settingsPath = settingsPath;
|
||||
this.isPackaged = isPackaged;
|
||||
this.getRunningEnvironmentUrls = getRunningEnvironmentUrls;
|
||||
this.getApiKey = getApiKey;
|
||||
this.promptConsent = promptConsent;
|
||||
this.onChange = onChange;
|
||||
this.settings = { enabled: false, consentMode: 'ask' };
|
||||
this.child = null;
|
||||
this.connectedUrls = new Set();
|
||||
this.currentTargets = [];
|
||||
this.stdoutBuffer = '';
|
||||
this.lastEvent = null;
|
||||
this.lastError = null;
|
||||
}
|
||||
|
||||
getSettings() {
|
||||
return { ...this.settings };
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
enabled: this.settings.enabled,
|
||||
consentMode: this.settings.consentMode,
|
||||
running: Boolean(this.child),
|
||||
connectedCount: this.connectedUrls.size,
|
||||
targetCount: this.currentTargets.length,
|
||||
targetUrls: [...this.currentTargets],
|
||||
lastEvent: this.lastEvent,
|
||||
lastError: this.lastError,
|
||||
};
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
try {
|
||||
const raw = await fs.readFile(this.settingsPath, 'utf8');
|
||||
const stored = JSON.parse(raw);
|
||||
this.settings = {
|
||||
enabled: Boolean(stored.enabled),
|
||||
consentMode: stored.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
};
|
||||
} catch {
|
||||
this.settings = { enabled: false, consentMode: 'ask' };
|
||||
}
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
async saveSettings(next) {
|
||||
this.settings = {
|
||||
enabled: Boolean(next.enabled),
|
||||
consentMode: next.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
};
|
||||
await fs.mkdir(path.dirname(this.settingsPath), { recursive: true });
|
||||
await fs.writeFile(this.settingsPath, JSON.stringify(this.settings, null, 2), 'utf8');
|
||||
await this.sync();
|
||||
this.onChange?.();
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const targets = this.settings.enabled ? (this.getRunningEnvironmentUrls?.() || []) : [];
|
||||
const enabledTargets = this.settings.enabled ? await filterEnabledComputerUseTargets(targets, this.getApiKey?.() || '') : [];
|
||||
const wsTargets = enabledTargets.map(toAgentWsUrl).filter(Boolean);
|
||||
|
||||
const sameTargets =
|
||||
wsTargets.length === this.currentTargets.length &&
|
||||
wsTargets.every((url) => this.currentTargets.includes(url));
|
||||
|
||||
if (!this.settings.enabled || wsTargets.length === 0) {
|
||||
this.stop();
|
||||
this.currentTargets = [];
|
||||
this.lastEvent = this.settings.enabled ? 'no-targets' : 'disabled';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.child && sameTargets) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentTargets = wsTargets;
|
||||
this.lastEvent = 'restarting';
|
||||
this.lastError = null;
|
||||
this.restart(wsTargets);
|
||||
}
|
||||
|
||||
restart(wsTargets) {
|
||||
this.stop();
|
||||
|
||||
const agentEntry = process.env.CLOUDCLI_COMPUTER_AGENT_ENTRY
|
||||
|| path.join(this.appRoot, 'dist-server', 'server', 'computer-use-agent.js');
|
||||
const runtime = getNodeRuntime(this.isPackaged);
|
||||
|
||||
this.child = spawn(runtime.command, [agentEntry], {
|
||||
cwd: this.appRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
...runtime.env,
|
||||
PATH: getDesktopPath(),
|
||||
CLOUDCLI_DESKTOP_AGENT_URLS: wsTargets.join(','),
|
||||
CLOUDCLI_DESKTOP_AGENT_API_KEY: this.getApiKey?.() || '',
|
||||
CLOUDCLI_COMPUTER_USE_CONSENT_MODE: this.settings.consentMode,
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
this.connectedUrls = new Set();
|
||||
|
||||
this.child.once('error', (error) => {
|
||||
console.error('[ComputerAgent] failed to start:', error.message);
|
||||
this.lastEvent = 'start-error';
|
||||
this.lastError = error.message;
|
||||
this.child = null;
|
||||
this.onChange?.();
|
||||
});
|
||||
|
||||
this.child.stdout?.on('data', (chunk) => this.handleStdout(String(chunk)));
|
||||
this.child.stderr?.on('data', (chunk) => {
|
||||
for (const line of String(chunk).split(/\r?\n/)) {
|
||||
if (line.trim()) {
|
||||
this.lastError = line.trim();
|
||||
console.error('[ComputerAgent]', line);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.child.once('exit', (code) => {
|
||||
console.log(`[ComputerAgent] exited (code ${code ?? 'null'})`);
|
||||
this.lastEvent = `exit:${code ?? 'null'}`;
|
||||
this.child = null;
|
||||
this.connectedUrls = new Set();
|
||||
this.onChange?.();
|
||||
});
|
||||
|
||||
this.onChange?.();
|
||||
}
|
||||
|
||||
handleStdout(chunk) {
|
||||
this.stdoutBuffer += chunk;
|
||||
const lines = this.stdoutBuffer.split('\n');
|
||||
this.stdoutBuffer = lines.pop() || '';
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed.startsWith(IPC_PREFIX)) {
|
||||
if (trimmed) console.log('[ComputerAgent]', trimmed);
|
||||
continue;
|
||||
}
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(trimmed.slice(IPC_PREFIX.length).trim());
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
void this.handleAgentEvent(payload);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAgentEvent(payload) {
|
||||
switch (payload.type) {
|
||||
case 'connected':
|
||||
this.connectedUrls.add(payload.url);
|
||||
this.lastEvent = 'connected';
|
||||
this.lastError = null;
|
||||
this.onChange?.();
|
||||
break;
|
||||
case 'disconnected':
|
||||
this.connectedUrls.delete(payload.url);
|
||||
this.lastEvent = 'disconnected';
|
||||
this.onChange?.();
|
||||
if (payload.reason && /computer use.*disabled/i.test(payload.reason)) {
|
||||
void this.sync().catch((error) => {
|
||||
this.lastError = error instanceof Error ? error.message : 'Failed to sync Computer Use targets.';
|
||||
this.onChange?.();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'starting':
|
||||
this.lastEvent = 'starting';
|
||||
this.lastError = null;
|
||||
this.onChange?.();
|
||||
break;
|
||||
case 'error':
|
||||
this.lastEvent = 'error';
|
||||
this.lastError = payload.message || 'Computer agent error.';
|
||||
this.onChange?.();
|
||||
break;
|
||||
case 'consent-request': {
|
||||
const allow = await this.promptConsent?.(payload.sessionId);
|
||||
this.sendToChild({ type: 'consent-response', sessionId: payload.sessionId, allow: Boolean(allow) });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendToChild(message) {
|
||||
if (this.child?.stdin?.writable) {
|
||||
this.child.stdin.write(`${IPC_PREFIX} ${JSON.stringify(message)}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
revokeSession(sessionId) {
|
||||
this.sendToChild({ type: 'revoke-session', sessionId });
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.child) return;
|
||||
const child = this.child;
|
||||
this.child = null;
|
||||
this.connectedUrls = new Set();
|
||||
try { child.kill('SIGTERM'); } catch { /* noop */ }
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,6 @@ import { ViewHost } from './viewHost.js';
|
||||
|
||||
const TITLEBAR_HEIGHT = 44;
|
||||
const AUTH_TOKEN_STORAGE_KEY = 'auth-token';
|
||||
// TODO: Re-enable Computer Use menus after fixing the MCP server connection
|
||||
// between the desktop app and the web UI.
|
||||
const COMPUTER_USE_MENUS_ENABLED = false;
|
||||
|
||||
function isAllowedPermissionOrigin(sourceUrl, controlPlaneUrl) {
|
||||
try {
|
||||
const source = new URL(sourceUrl);
|
||||
@@ -437,17 +433,6 @@ export class DesktopWindowManager {
|
||||
accelerator: 'CmdOrCtrl+Shift+E',
|
||||
click: () => void this.actions.showEnvironmentPicker().catch((error) => this.actions.showError('Could not switch environment', error)),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Services',
|
||||
visible: COMPUTER_USE_MENUS_ENABLED,
|
||||
submenu: [
|
||||
{
|
||||
label: 'Computer Use',
|
||||
click: () => void this.showDesktopSettings(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Diagnostics',
|
||||
submenu: [
|
||||
|
||||
@@ -8,14 +8,6 @@ window.__MOCK_STATE__ = {
|
||||
shareableWebUrl: 'http://localhost:3001',
|
||||
localServerRunning: false,
|
||||
localStartupLogs: [],
|
||||
computerUse: { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 },
|
||||
computerUsePermissions: {
|
||||
platform: 'darwin',
|
||||
supported: true,
|
||||
accessibility: 'not_granted',
|
||||
screenRecording: 'not_determined',
|
||||
message: 'macOS requires Accessibility and Screen Recording for Computer Use.',
|
||||
},
|
||||
environments: [
|
||||
{ id: 'env-api', name: 'api-gateway', subdomain: 'api-gateway', access_url: 'https://api-gateway.cloudcli.ai', status: 'running', region: 'fra1', agent: 'Claude Code' },
|
||||
{ id: 'env-web', name: 'web-frontend', subdomain: 'web-frontend', access_url: 'https://web-frontend.cloudcli.ai', status: 'stopped', region: 'sfo1', agent: 'Codex' },
|
||||
@@ -62,7 +54,6 @@ window.__MOCK_STATE__ = {
|
||||
refreshEnvironments: function () { return Promise.resolve(clone(mockState)); },
|
||||
refreshActiveTab: function () { return Promise.resolve(clone(mockState)); },
|
||||
copyDiagnostics: function () { return Promise.resolve(clone(mockState)); },
|
||||
showComputerAccess: function () { return Promise.resolve(clone(mockState)); },
|
||||
showEnvironmentPicker: function () { return Promise.resolve(clone(mockState)); },
|
||||
showLauncher: function () { return Promise.resolve(clone(mockState)); },
|
||||
showLocalSettings: function () { return Promise.resolve(clone(mockState)); },
|
||||
@@ -82,23 +73,6 @@ window.__MOCK_STATE__ = {
|
||||
mockState.desktopSettings[key] = key === 'themeMode' ? value : !!value;
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
updateComputerUse: function (settings) {
|
||||
mockState.computerUse = mockState.computerUse || { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 };
|
||||
if (typeof settings.enabled === 'boolean') mockState.computerUse.enabled = settings.enabled;
|
||||
if (settings.consentMode === 'auto' || settings.consentMode === 'ask') mockState.computerUse.consentMode = settings.consentMode;
|
||||
mockState.computerUse.running = mockState.computerUse.enabled;
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
requestComputerUsePermission: function (permission) {
|
||||
mockState.computerUsePermissions = mockState.computerUsePermissions || {};
|
||||
if (permission === 'accessibility') mockState.computerUsePermissions.accessibility = 'granted';
|
||||
if (permission === 'screen') mockState.computerUsePermissions.screenRecording = 'granted';
|
||||
if (permission === 'all') {
|
||||
mockState.computerUsePermissions.accessibility = 'granted';
|
||||
mockState.computerUsePermissions.screenRecording = 'granted';
|
||||
}
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
openEnvironment: function (id) {
|
||||
var env = (mockState.environments || []).filter(function (item) { return item.id === id; })[0];
|
||||
if (env) {
|
||||
@@ -189,22 +163,6 @@ window.__MOCK_STATE__ = {
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function computerUseStatus(state) {
|
||||
var computerUse = state && state.computerUse ? state.computerUse : {};
|
||||
var connectedCount = computerUse.connectedCount || 0;
|
||||
var environmentLabel = connectedCount + ' environment' + (connectedCount === 1 ? '' : 's');
|
||||
if (!computerUse.enabled) {
|
||||
return { label: 'Disabled', tone: 'idle', detail: 'CloudCLI cannot use this computer.' };
|
||||
}
|
||||
if (!connectedCount) {
|
||||
return { label: 'Not connected', tone: 'warn', detail: 'No environment connected.' };
|
||||
}
|
||||
if (computerUse.consentMode === 'auto') {
|
||||
return { label: 'Connected', tone: 'warn', detail: environmentLabel + ' connected. Unattended access is on.' };
|
||||
}
|
||||
return { label: 'Connected', tone: 'ok', detail: environmentLabel + ' connected.' };
|
||||
}
|
||||
|
||||
var CC = {
|
||||
icon: icon,
|
||||
esc: esc,
|
||||
@@ -214,7 +172,6 @@ window.__MOCK_STATE__ = {
|
||||
accountLabel: accountLabel,
|
||||
localUrl: localUrl,
|
||||
envCount: envCount,
|
||||
computerUseStatus: computerUseStatus,
|
||||
version: VERSION,
|
||||
logoUrl: LOGO_URL,
|
||||
platform: 'win',
|
||||
@@ -352,42 +309,12 @@ window.__MOCK_STATE__ = {
|
||||
return CC.run('Saved', function () { return bridge.updateSetting(node.key, node.value); });
|
||||
case 'set-theme-mode':
|
||||
return CC.run('Saved', function () { return bridge.updateSetting('themeMode', node.value); });
|
||||
case 'set-computer-mode':
|
||||
CC.state.computerUse = {
|
||||
...((CC.state && CC.state.computerUse) || {}),
|
||||
enabled: true,
|
||||
consentMode: node.value === 'auto' ? 'auto' : 'ask',
|
||||
};
|
||||
return CC.run('Saved', function () {
|
||||
return bridge.updateComputerUse({
|
||||
enabled: true,
|
||||
consentMode: node.value,
|
||||
});
|
||||
});
|
||||
case 'set-computer-enabled':
|
||||
CC.state.computerUse = {
|
||||
...((CC.state && CC.state.computerUse) || {}),
|
||||
enabled: !!node.value,
|
||||
};
|
||||
return CC.run('Saved', function () {
|
||||
var current = (CC.state && CC.state.computerUse) || { consentMode: 'ask' };
|
||||
return bridge.updateComputerUse({
|
||||
enabled: !!node.value,
|
||||
consentMode: current.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
});
|
||||
});
|
||||
case 'computer-permission':
|
||||
return CC.run('Opening permission settings...', function () {
|
||||
return bridge.requestComputerUsePermission(node.getAttribute('data-cc-computer-permission'));
|
||||
});
|
||||
case 'settings-toggle':
|
||||
return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); });
|
||||
case 'desktop-settings-toggle':
|
||||
return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); });
|
||||
case 'local-settings-toggle':
|
||||
return CC.run('Opening local settings...', function () { return bridge.showLocalSettings(); });
|
||||
case 'computer-settings-toggle':
|
||||
return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); });
|
||||
case 'settings-close':
|
||||
return CC.closeSheet();
|
||||
case 'dashboard':
|
||||
@@ -541,62 +468,6 @@ window.__MOCK_STATE__ = {
|
||||
);
|
||||
};
|
||||
|
||||
function permissionLabel(value) {
|
||||
if (value === 'granted') return 'Granted';
|
||||
if (value === 'denied' || value === 'restricted') return 'Needs attention';
|
||||
if (value === 'not_applicable') return 'Not required';
|
||||
return 'Not granted';
|
||||
}
|
||||
|
||||
function permissionTone(value) {
|
||||
if (value === 'granted' || value === 'not_applicable') return 'ok';
|
||||
if (value === 'denied' || value === 'restricted') return 'warn';
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
// TODO: Re-enable Computer Use menus after fixing the MCP server connection
|
||||
// between the desktop app and the web UI.
|
||||
var COMPUTER_USE_MENUS_ENABLED = false;
|
||||
|
||||
function renderComputerPermissionRow(key, label, detail, status) {
|
||||
return '<div class="cc-permission-row">' +
|
||||
'<div><div class="cc-permission-title">' + CC.esc(label) + '</div><div class="cc-permission-detail">' + CC.esc(detail) + '</div></div>' +
|
||||
'<div class="cc-permission-actions"><span class="badge ' + permissionTone(status) + '">' + CC.esc(permissionLabel(status)) + '</span>' +
|
||||
(status === 'granted' || status === 'not_applicable'
|
||||
? ''
|
||||
: '<button class="btn sm" data-cc-action="computer-permission" data-cc-computer-permission="' + CC.esc(key) + '">Open settings</button>') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function renderComputerPermissions(state) {
|
||||
var permissions = state.computerUsePermissions || {};
|
||||
if (!permissions.supported) {
|
||||
return '<div class="cc-note">' + CC.esc(permissions.message || 'No additional OS permission setup is required from CloudCLI on this platform.') + '</div>';
|
||||
}
|
||||
return '<div class="cc-note">' + CC.esc(permissions.message || 'Grant the required OS permissions before approving agent control.') + '</div>' +
|
||||
renderComputerPermissionRow('accessibility', 'Accessibility', 'Allows CloudCLI to click, type, and use accessibility actions.', permissions.accessibility) +
|
||||
renderComputerPermissionRow('screen', 'Screen Recording', 'Allows CloudCLI to capture screenshots for agent observation.', permissions.screenRecording);
|
||||
}
|
||||
|
||||
CC.buildComputerUseSection = function (state) {
|
||||
var computerUse = state.computerUse || {};
|
||||
var status = computerUseStatus(state);
|
||||
var body =
|
||||
'<div class="cc-surface">' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-computer-enabled="true"' + (computerUse.enabled ? ' checked' : '') + '><span><b>Enable Computer Use</b><br>Let CloudCLI use the computer. Agents cannot act until you approve a session.</span></label>' +
|
||||
'<div class="cc-row2"><span class="badge ' + CC.esc(status.tone) + '">' + CC.esc(status.label) + '</span><span class="cc-meta">' + CC.esc(status.detail) + '</span><button class="btn sm" data-cc-action="refresh-environments">' + CC.icon('refresh', 14) + 'Refresh / relink</button></div>';
|
||||
if (computerUse.enabled) {
|
||||
body += '<div class="cc-permissions">' + renderComputerPermissions(state) + '</div>';
|
||||
body += '<div class="cc-choice-group">' +
|
||||
CC.renderRadioOption('computer-access-mode', 'ask', computerUse.consentMode !== 'auto', 'Ask before each session', 'Agents can request control, but you approve every session.') +
|
||||
CC.renderRadioOption('computer-access-mode', 'auto', computerUse.consentMode === 'auto', 'Unattended access', 'Trusted agents can use this computer without a local approval prompt.') +
|
||||
'</div>';
|
||||
}
|
||||
body += '</div>';
|
||||
return CC.renderSection('COMPUTER USE', 'Control how agents can use this computer', body);
|
||||
};
|
||||
|
||||
CC.renderLocalSettings = function () {
|
||||
var state = CC.state || {};
|
||||
var sections = [
|
||||
@@ -612,13 +483,9 @@ window.__MOCK_STATE__ = {
|
||||
};
|
||||
|
||||
CC.renderDesktopSettings = function () {
|
||||
var state = CC.state || {};
|
||||
var sections = [
|
||||
CC.buildThemeSection(state),
|
||||
CC.buildThemeSection(CC.state || {}),
|
||||
];
|
||||
if (COMPUTER_USE_MENUS_ENABLED) {
|
||||
sections.push(CC.buildComputerUseSection(state));
|
||||
}
|
||||
CC.renderSheet('Desktop Settings', 'Manage the desktop app appearance.', sections);
|
||||
};
|
||||
|
||||
@@ -681,15 +548,6 @@ window.__MOCK_STATE__ = {
|
||||
CC.act('set-theme-mode', { value: theme.value });
|
||||
return;
|
||||
}
|
||||
var computerMode = event.target.closest('[name="computer-access-mode"]');
|
||||
if (computerMode) {
|
||||
CC.act('set-computer-mode', { value: computerMode.value });
|
||||
return;
|
||||
}
|
||||
var computerEnabled = event.target.closest('[data-cc-computer-enabled]');
|
||||
if (computerEnabled) {
|
||||
CC.act('set-computer-enabled', { value: computerEnabled.checked });
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
|
||||
118
electron/main.js
118
electron/main.js
@@ -1,10 +1,9 @@
|
||||
import { app, BrowserWindow, clipboard, dialog, ipcMain, session, shell, systemPreferences } from 'electron';
|
||||
import { app, BrowserWindow, clipboard, dialog, ipcMain, session, shell } from 'electron';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { CloudController } from './cloud.js';
|
||||
import { ComputerAgentController } from './computerAgent.js';
|
||||
import { DesktopWindowManager } from './desktopWindow.js';
|
||||
import { DesktopNotificationsController } from './desktopNotifications.js';
|
||||
import { LocalServerController } from './localServer.js';
|
||||
@@ -30,7 +29,6 @@ let activeTarget = { kind: 'launcher', name: APP_NAME, url: null };
|
||||
let desktopWindow = null;
|
||||
let localServer = null;
|
||||
let cloud = null;
|
||||
let computerAgent = null;
|
||||
let desktopNotifications = null;
|
||||
let isQuitting = false;
|
||||
let isRefreshingCloud = false;
|
||||
@@ -63,10 +61,6 @@ function getSettingsPath() {
|
||||
return path.join(app.getPath('userData'), 'desktop-settings.json');
|
||||
}
|
||||
|
||||
function getComputerUseSettingsPath() {
|
||||
return path.join(app.getPath('userData'), 'computer-use-settings.json');
|
||||
}
|
||||
|
||||
function getDesktopNotificationsSettingsPath() {
|
||||
return path.join(app.getPath('userData'), 'desktop-notifications-settings.json');
|
||||
}
|
||||
@@ -78,23 +72,6 @@ function getRunningEnvironmentUrls() {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
async function promptComputerUseConsent(sessionId) {
|
||||
const { response } = await dialog.showMessageBox(desktopWindow?.getMainWindow() || undefined, {
|
||||
type: 'warning',
|
||||
buttons: ['Allow this session', 'Deny'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
title: 'Computer Use request',
|
||||
message: 'An agent wants to control this computer',
|
||||
detail: [
|
||||
'A cloud agent is requesting control of your mouse, keyboard, and screen for this session.',
|
||||
'Approval lasts for this session only. You can stop it any time from the Computer panel.',
|
||||
sessionId ? `\nSession: ${sessionId}` : '',
|
||||
].join('\n'),
|
||||
});
|
||||
return response === 0;
|
||||
}
|
||||
|
||||
function getDisplayTargetName() {
|
||||
return activeTarget?.name || APP_NAME;
|
||||
}
|
||||
@@ -151,66 +128,10 @@ function getDesktopState() {
|
||||
tabs: tabs.getSerializableTabs(),
|
||||
activeTabId: tabs.activeTabId,
|
||||
environments: cloud.getEnvironments().map(serializeEnvironment),
|
||||
computerUse: computerAgent?.getState() || { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 },
|
||||
desktopNotifications: desktopNotifications?.getState() || { enabled: false, supported: false, connectedCount: 0, targetCount: 0 },
|
||||
computerUsePermissions: getComputerUsePermissions(),
|
||||
};
|
||||
}
|
||||
|
||||
function getComputerUsePermissions() {
|
||||
if (process.platform !== 'darwin') {
|
||||
return {
|
||||
platform: process.platform,
|
||||
supported: false,
|
||||
accessibility: 'not_applicable',
|
||||
screenRecording: 'not_applicable',
|
||||
message: 'No OS permission onboarding is required from CloudCLI on this platform.',
|
||||
};
|
||||
}
|
||||
|
||||
let accessibility;
|
||||
let screenRecording;
|
||||
try {
|
||||
accessibility = systemPreferences.isTrustedAccessibilityClient(false) ? 'granted' : 'not_granted';
|
||||
} catch {
|
||||
accessibility = 'unknown';
|
||||
}
|
||||
try {
|
||||
screenRecording = systemPreferences.getMediaAccessStatus('screen');
|
||||
} catch {
|
||||
screenRecording = 'unknown';
|
||||
}
|
||||
|
||||
return {
|
||||
platform: 'darwin',
|
||||
supported: true,
|
||||
accessibility,
|
||||
screenRecording,
|
||||
message: accessibility === 'granted' && screenRecording === 'granted'
|
||||
? 'macOS permissions are granted.'
|
||||
: 'macOS requires Accessibility and Screen Recording for Computer Use.',
|
||||
};
|
||||
}
|
||||
|
||||
async function requestComputerUsePermission(permission) {
|
||||
if (process.platform !== 'darwin') {
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
if (permission === 'accessibility') {
|
||||
systemPreferences.isTrustedAccessibilityClient(true);
|
||||
} else if (permission === 'screen') {
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
||||
} else if (permission === 'all') {
|
||||
systemPreferences.isTrustedAccessibilityClient(true);
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
||||
} else {
|
||||
throw new Error(`Unknown Computer Use permission: ${permission}`);
|
||||
}
|
||||
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
async function openExternalUrl(url) {
|
||||
if (String(url).startsWith(CALLBACK_PROTOCOL + "://")) {
|
||||
await handleDeepLink(url);
|
||||
@@ -316,8 +237,6 @@ function getDiagnosticsText() {
|
||||
cloudEnvironmentCount: cloud.getEnvironments().length,
|
||||
cloudRunningEnvironmentCount: getRunningEnvironmentUrls().length,
|
||||
cloudAuthState: cloud.getAuthState(),
|
||||
computerUse: computerAgent?.getState() || null,
|
||||
computerUseSettingsPath: getComputerUseSettingsPath(),
|
||||
cloudAccountPath: getStorePath(),
|
||||
controlPlaneUrl: CLOUDCLI_CONTROL_PLANE_URL,
|
||||
}, null, 2);
|
||||
@@ -332,22 +251,6 @@ async function copyDiagnostics() {
|
||||
});
|
||||
}
|
||||
|
||||
async function showComputerAccess() {
|
||||
await desktopWindow?.showDesktopSettings();
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
async function updateComputerUse(settings) {
|
||||
const current = computerAgent?.getSettings() || { enabled: false, consentMode: 'ask' };
|
||||
const next = {
|
||||
enabled: typeof settings?.enabled === 'boolean' ? settings.enabled : current.enabled,
|
||||
consentMode: settings?.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
};
|
||||
await computerAgent?.saveSettings(next);
|
||||
syncDesktopState();
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
async function refreshCloudEnvironments({ showErrors = false } = {}) {
|
||||
isRefreshingCloud = true;
|
||||
syncDesktopState();
|
||||
@@ -370,7 +273,6 @@ async function refreshCloudEnvironments({ showErrors = false } = {}) {
|
||||
throw error;
|
||||
} finally {
|
||||
isRefreshingCloud = false;
|
||||
void computerAgent?.sync().catch((error) => console.error('[ComputerAgent] sync failed:', error?.message || error));
|
||||
void desktopNotifications?.sync().catch((error) => console.error('[DesktopNotifications] sync failed:', error?.message || error));
|
||||
syncDesktopState();
|
||||
}
|
||||
@@ -852,16 +754,10 @@ function registerIpcHandlers() {
|
||||
await desktopWindow.showLauncher();
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:show-computer-access', async () => {
|
||||
await showComputerAccess();
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:update-computer-use', async (_event, settings) => updateComputerUse(settings));
|
||||
ipcMain.handle('cloudcli-desktop:update-desktop-notifications', async (_event, settings) => {
|
||||
await desktopNotifications?.saveSettings(settings);
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:request-computer-use-permission', async (_event, permission) => requestComputerUsePermission(permission));
|
||||
ipcMain.handle('cloudcli-desktop:show-desktop-settings', async () => desktopWindow.showDesktopSettings());
|
||||
ipcMain.handle('cloudcli-desktop:show-local-settings', async () => desktopWindow.showLocalSettings());
|
||||
ipcMain.handle('cloudcli-desktop:close-settings-window', async () => {
|
||||
@@ -899,7 +795,6 @@ function registerAppEvents() {
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
computerAgent?.stop();
|
||||
desktopNotifications?.stop();
|
||||
});
|
||||
|
||||
@@ -951,7 +846,6 @@ async function createDesktopWindow() {
|
||||
openCloudDashboard,
|
||||
refreshCloudEnvironments: () => refreshCloudEnvironments({ showErrors: true }),
|
||||
setActiveTarget,
|
||||
showComputerAccess,
|
||||
showEnvironmentPicker,
|
||||
showError,
|
||||
startEnvironment,
|
||||
@@ -1017,15 +911,6 @@ async function bootstrap() {
|
||||
callbackUrl: CALLBACK_URL,
|
||||
onChange: syncDesktopState,
|
||||
});
|
||||
computerAgent = new ComputerAgentController({
|
||||
appRoot: getAppRoot(),
|
||||
settingsPath: getComputerUseSettingsPath(),
|
||||
isPackaged: app.isPackaged,
|
||||
getRunningEnvironmentUrls,
|
||||
getApiKey: () => cloud.getAccount()?.apiKey || '',
|
||||
promptConsent: promptComputerUseConsent,
|
||||
onChange: syncDesktopState,
|
||||
});
|
||||
desktopNotifications = new DesktopNotificationsController({
|
||||
settingsPath: getDesktopNotificationsSettingsPath(),
|
||||
appVersion: app.getVersion(),
|
||||
@@ -1042,7 +927,6 @@ async function bootstrap() {
|
||||
|
||||
await localServer.loadDesktopSettings();
|
||||
await cloud.loadCloudAccount();
|
||||
await computerAgent.loadSettings();
|
||||
await desktopNotifications.loadSettings();
|
||||
|
||||
registerProtocolHandler();
|
||||
|
||||
@@ -44,10 +44,7 @@ if (window.location.protocol === 'file:') {
|
||||
refreshActiveTab: () => ipcRenderer.invoke('cloudcli-desktop:reload-active-tab'),
|
||||
showEnvironmentPicker: () => ipcRenderer.invoke('cloudcli-desktop:show-environment-picker'),
|
||||
showLauncher: () => ipcRenderer.invoke('cloudcli-desktop:show-launcher'),
|
||||
showComputerAccess: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-access'),
|
||||
showLocalSettings: () => ipcRenderer.invoke('cloudcli-desktop:show-local-settings'),
|
||||
updateComputerUse: (settings) => ipcRenderer.invoke('cloudcli-desktop:update-computer-use', settings),
|
||||
requestComputerUsePermission: (permission) => ipcRenderer.invoke('cloudcli-desktop:request-computer-use-permission', permission),
|
||||
showDesktopSettings: () => ipcRenderer.invoke('cloudcli-desktop:show-desktop-settings'),
|
||||
closeSettingsWindow: () => ipcRenderer.invoke('cloudcli-desktop:close-settings-window'),
|
||||
showActiveEnvironmentActionsMenu: () => ipcRenderer.invoke('cloudcli-desktop:show-active-environment-actions-menu'),
|
||||
|
||||
Reference in New Issue
Block a user