import { createRequire } from 'node:module'; import { randomBytes, randomUUID } from 'node:crypto'; import { spawn } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { appConfigDb } from '@/modules/database/repositories/app-config.js'; import { providerMcpService } from '@/modules/providers/services/mcp.service.js'; import { getModuleDir } from '@/utils/runtime-paths.js'; import { executor, captureScreenshot as captureScreenshotRuntime, getRuntimeReadiness as getExecutorReadiness, type Point, type ClickButton, type ScrollDirection, } from '@/modules/computer-use/computer-executor.js'; import { desktopAgentRelay } from '@/modules/computer-use/desktop-agent-relay.service.js'; const __dirname = getModuleDir(import.meta.url); const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true'; const MAX_SESSIONS_PER_OWNER = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_MAX_SESSIONS_PER_OWNER || '1', 10); const SESSION_TTL_MS = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_SESSION_TTL_MS || String(30 * 60 * 1000), 10); const COMPUTER_USE_SETTINGS_KEY = 'computer_use_settings'; const COMPUTER_USE_MCP_TOKEN_KEY = 'computer_use_mcp_token'; type ComputerUseRuntime = 'cloud' | 'local'; type ComputerUseSessionStatus = 'ready' | 'stopped' | 'unavailable'; type ComputerUseSession = { id: string; ownerId: string; createdBy: 'user' | 'agent'; runtime: ComputerUseRuntime; status: ComputerUseSessionStatus; screenshotDataUrl: string | null; createdAt: string; updatedAt: string; lastAction: string | null; message: string | null; /** Per-session consent: agents may act only while this is true. */ agentAccessEnabled: boolean; /** Size of the captured screenshot in pixels — the coordinate space agents/users use. */ displaySize: { width: number; height: number; } | null; cursor: { x: number; y: number; actor: 'agent' | 'user'; } | null; }; type PublicComputerUseSession = Omit; type ComputerUseOwner = { id: string | number; }; type ComputerUseSettings = { enabled: boolean; agentToolsEnabled: boolean; }; type RuntimeReadiness = { nut: any | null; screenshot: any | null; nutInstalled: boolean; screenshotInstalled: boolean; installInProgress: boolean; installMessage: string | null; }; const sessions = new Map(); let installPromise: Promise<{ success: boolean; message: string }> | null = null; let lastInstallMessage: string | null = null; const DEFAULT_SETTINGS: ComputerUseSettings = { enabled: false, agentToolsEnabled: false, }; const AGENT_OWNER_ID = 'agent'; const MCP_SERVER_NAME = 'cloudcli-computer-use'; const MCP_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'opencode']; function getRuntime(): ComputerUseRuntime { return IS_PLATFORM ? 'cloud' : 'local'; } function readSettings(): ComputerUseSettings { try { const raw = appConfigDb.get(COMPUTER_USE_SETTINGS_KEY); if (!raw) { return DEFAULT_SETTINGS; } const parsed = JSON.parse(raw) as Partial; return { enabled: parsed.enabled === true, agentToolsEnabled: parsed.agentToolsEnabled === true, }; } catch (error: any) { console.warn('[Computer Use] Failed to read settings:', error?.message || error); return DEFAULT_SETTINGS; } } function writeSettings(settings: ComputerUseSettings): ComputerUseSettings { const normalized = { enabled: settings.enabled === true, agentToolsEnabled: settings.agentToolsEnabled === true, }; appConfigDb.set(COMPUTER_USE_SETTINGS_KEY, JSON.stringify(normalized)); return normalized; } function getOrCreateMcpToken(): string { const existing = appConfigDb.get(COMPUTER_USE_MCP_TOKEN_KEY); if (existing) { return existing; } const token = randomBytes(32).toString('hex'); appConfigDb.set(COMPUTER_USE_MCP_TOKEN_KEY, token); return token; } function getSetupMessage(settings: ComputerUseSettings, readiness: RuntimeReadiness): string { if (getRuntime() === 'cloud') { return 'Cloud Computer Use requires a linked CloudCLI Desktop Agent on the user machine.'; } if (!settings.enabled) { return 'Computer Use is disabled in settings.'; } if (!readiness.nutInstalled || !readiness.screenshotInstalled) { return 'Install the desktop control runtime to capture the screen and drive the mouse and keyboard.'; } return readiness.installMessage || 'Computer Use runtime is not ready.'; } function getMcpCommand(): { command: string; args: string[] } { const serverDir = path.resolve(__dirname, '..', '..'); const mcpScriptPath = path.join(serverDir, 'computer-use-mcp.js'); if (fs.existsSync(mcpScriptPath)) { return { command: process.execPath, args: [mcpScriptPath], }; } return { command: 'cloudcli', args: ['computer-use-mcp'], }; } function getMcpApiUrl(): string { const port = process.env.SERVER_PORT || process.env.PORT || '3001'; return `http://127.0.0.1:${port}/api/computer-use-mcp`; } function getRuntimeReadiness(): RuntimeReadiness { const base = getExecutorReadiness(); return { ...base, installInProgress: Boolean(installPromise), installMessage: lastInstallMessage, }; } function runCommand(command: string, args: string[]): Promise { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: process.cwd(), env: process.env, shell: false, stdio: ['ignore', 'pipe', 'pipe'], }); const output: string[] = []; child.stdout.on('data', (chunk) => output.push(String(chunk))); child.stderr.on('data', (chunk) => output.push(String(chunk))); child.on('error', reject); child.on('close', (code) => { if (code === 0) { resolve(); return; } reject(new Error(output.join('').trim() || `${command} ${args.join(' ')} exited with code ${code}`)); }); }); } function formatInstallError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); if (process.platform === 'linux' && /libxtst|x11|xtst|libpng|imagemagick|scrot/i.test(message)) { return [ 'Installing the desktop control runtime needs system packages.', 'On Debian/Ubuntu run: sudo apt-get install -y libxtst-dev libpng-dev imagemagick', 'then try again.', ].join(' '); } return message || 'Failed to install the Computer Use runtime.'; } function isPackagedElectronNodeRuntime(): boolean { return process.env.ELECTRON_RUN_AS_NODE === '1' && Boolean(process.versions.electron); } async function installRuntime(): Promise<{ success: boolean; message: string }> { if (installPromise) { return installPromise; } const readiness = getExecutorReadiness(); if (readiness.nutInstalled && readiness.screenshotInstalled) { lastInstallMessage = 'Computer Use runtime is available.'; return { success: true, message: lastInstallMessage }; } if (isPackagedElectronNodeRuntime()) { lastInstallMessage = 'Computer Use runtime was not bundled with this desktop build.'; return { success: false, message: lastInstallMessage }; } const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; installPromise = (async () => { try { lastInstallMessage = 'Installing desktop control runtime…'; await runCommand(npmCommand, [ 'install', '--no-save', '--no-package-lock', '@nut-tree-fork/nut-js', 'screenshot-desktop', ]); lastInstallMessage = 'Computer Use runtime installed.'; return { success: true, message: lastInstallMessage }; } catch (error) { lastInstallMessage = formatInstallError(error); return { success: false, message: lastInstallMessage }; } })(); try { return await installPromise; } finally { installPromise = null; } } function getOwnerId(owner: ComputerUseOwner): string { if (owner.id === undefined || owner.id === null || String(owner.id).trim() === '') { throw new Error('Authenticated user is required.'); } return String(owner.id); } function publicSession(session: ComputerUseSession): PublicComputerUseSession { const { ownerId: _ownerId, ...publicFields } = session; return publicFields; } function ownerSessions(ownerId: string): ComputerUseSession[] { return [...sessions.values()].filter((session) => session.ownerId === ownerId); } function canAccessSession(ownerId: string, session: ComputerUseSession): boolean { return session.ownerId === ownerId || session.ownerId === AGENT_OWNER_ID; } async function expireStaleSessions(now = Date.now()): Promise { for (const session of sessions.values()) { if (session.status !== 'ready') { continue; } const updatedAt = Date.parse(session.updatedAt); if (!Number.isFinite(updatedAt) || now - updatedAt <= SESSION_TTL_MS) { continue; } session.status = 'stopped'; session.agentAccessEnabled = false; session.updatedAt = new Date(now).toISOString(); session.lastAction = 'expire'; session.message = 'Computer Use session expired after inactivity.'; } } // --- Action layer: local executor (OSS) or cloud relay to the desktop agent -- // // Every desktop interaction goes through `performAction` / `getCursorPosition`. // In local mode it drives the in-process nut-js executor (computer-executor.ts); // in cloud mode it forwards the action to the linked desktop agent over // `desktopAgentRelay` and applies the returned screenshot. The local server // itself never touches the OS in cloud mode. /** One desktop interaction expressed in screenshot-pixel coordinate space. */ export type ComputerAction = | { type: 'screenshot' } | { type: 'mouse_move'; point: Point } | { type: 'click'; button: ClickButton; point?: Point; double?: boolean } | { type: 'drag'; from: Point; to: Point; button?: ClickButton } | { type: 'type'; text: string } | { type: 'key'; key: string } | { type: 'scroll'; direction: ScrollDirection; amount?: number; point?: Point } | { type: 'wait'; ms?: number }; /** Shape the desktop agent returns for any relayed action. */ type RelayResult = { screenshotDataUrl?: string | null; displaySize?: { width: number; height: number } | null; cursor?: { x: number; y: number } | null; position?: Point | null; }; function applyRelayResult(session: ComputerUseSession, result: RelayResult): void { if (typeof result.screenshotDataUrl === 'string') { session.screenshotDataUrl = result.screenshotDataUrl; } if (result.displaySize) { session.displaySize = result.displaySize; } if (result.cursor) { session.cursor = { x: result.cursor.x, y: result.cursor.y, actor: session.cursor?.actor ?? 'agent' }; } session.updatedAt = new Date().toISOString(); } async function refreshScreenshot(session: ComputerUseSession): Promise { if (getRuntime() === 'cloud') { const result = (await desktopAgentRelay.relay('screenshot', { sessionId: session.id })) as RelayResult; applyRelayResult(session, result); return; } const { dataUrl, size } = await captureScreenshotRuntime(); session.screenshotDataUrl = dataUrl; if (size) { session.displaySize = size; } session.updatedAt = new Date().toISOString(); } /** Runs one action and refreshes the session screenshot afterwards. */ async function performAction(session: ComputerUseSession, action: ComputerAction): Promise { if (getRuntime() === 'cloud') { const result = (await desktopAgentRelay.relay(action.type, { ...action, sessionId: session.id, displaySize: session.displaySize, })) as RelayResult; applyRelayResult(session, result); return; } switch (action.type) { case 'screenshot': break; case 'mouse_move': await executor.moveTo(session, action.point); break; case 'click': await executor.click(session, action.button, action.point, action.double === true); break; case 'drag': await executor.drag(session, action.from, action.to, action.button ?? 'left'); break; case 'type': await executor.type(action.text); break; case 'key': await executor.pressChord(action.key); break; case 'scroll': await executor.scroll(session, action.direction, action.amount ?? 3, action.point); break; case 'wait': await new Promise((resolve) => setTimeout(resolve, Math.max(0, Math.min(action.ms ?? 1000, 10_000)))); break; } await refreshScreenshot(session); } /** Reads the current cursor position in screenshot-pixel space. */ async function getCursorPosition(session: ComputerUseSession): Promise { if (getRuntime() === 'cloud') { const result = (await desktopAgentRelay.relay('cursor_position', { sessionId: session.id, displaySize: session.displaySize, })) as RelayResult; applyRelayResult(session, result); if (result.position) { return result.position; } return session.cursor ? { x: session.cursor.x, y: session.cursor.y } : { x: 0, y: 0 }; } return executor.cursorPosition(session); } function assertReady(session: ComputerUseSession): void { if (session.status !== 'ready') { throw new Error(session.message || 'Computer Use session is not available.'); } } /** * Whether agent tools may operate right now. Cloud mode depends purely on a * connected desktop agent; local mode depends on the two opt-in settings. */ function agentToolsAvailable(): boolean { if (getRuntime() === 'cloud') { return desktopAgentRelay.isConnected(); } const settings = readSettings(); return settings.enabled && settings.agentToolsEnabled; } function assertAgentToolsAvailable(): void { if (agentToolsAvailable()) { return; } throw new Error( getRuntime() === 'cloud' ? 'No desktop agent is connected. Open the CloudCLI desktop app with Computer Use enabled.' : 'Computer Use agent tools are disabled.' ); } export const computerUseService = { async getSettings() { return readSettings(); }, async updateSettings(settings: Partial) { const current = readSettings(); const nextSettings = { ...current, enabled: typeof settings.enabled === 'boolean' ? settings.enabled : current.enabled, agentToolsEnabled: typeof settings.agentToolsEnabled === 'boolean' ? settings.agentToolsEnabled : current.agentToolsEnabled, }; if (!nextSettings.enabled) { nextSettings.agentToolsEnabled = false; } const next = writeSettings(nextSettings); if (next.agentToolsEnabled) { await this.registerAgentMcp(); } else if (current.agentToolsEnabled) { await this.unregisterAgentMcp(); } return next; }, async getStatus() { const settings = readSettings(); 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. const desktopAgentConnected = desktopAgentRelay.isConnected(); const available = isCloud ? desktopAgentConnected : settings.enabled && runtimeReady; return { enabled: isCloud ? true : settings.enabled, runtime: getRuntime(), available, requiresDesktopBridge: isCloud, desktopAgentConnected, nutInstalled: readiness.nutInstalled, screenshotInstalled: readiness.screenshotInstalled, installInProgress: readiness.installInProgress, sessionCount: sessions.size, agentToolsEnabled: isCloud ? desktopAgentConnected : settings.agentToolsEnabled, mcpRecommended: !settings.agentToolsEnabled, message: available ? 'Computer Use runtime is available.' : getSetupMessage(settings, readiness), }; }, async registerAgentMcp() { const { command, args } = getMcpCommand(); const results = await providerMcpService.addMcpServerToAllProviders({ name: MCP_SERVER_NAME, scope: 'user', transport: 'stdio', command, args, env: { CLOUDCLI_COMPUTER_USE_MCP_TOKEN: getOrCreateMcpToken(), CLOUDCLI_COMPUTER_USE_API_URL: getMcpApiUrl(), }, }); return { name: MCP_SERVER_NAME, command, args, results }; }, getMcpToken() { return getOrCreateMcpToken(); }, 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', }; } })); return { name: MCP_SERVER_NAME, results }; }, async installRuntime() { const result = await installRuntime(); return { ...result, status: await this.getStatus(), }; }, async listSessions(owner: ComputerUseOwner) { const ownerId = getOwnerId(owner); await expireStaleSessions(); return [...sessions.values()] .filter((session) => canAccessSession(ownerId, session)) .map(publicSession); }, async createSession(owner: ComputerUseOwner, options?: { createdBy?: 'user' | 'agent' }) { const ownerId = getOwnerId(owner); await expireStaleSessions(); const createdBy = options?.createdBy ?? 'user'; const now = new Date().toISOString(); const session: ComputerUseSession = { id: randomUUID(), ownerId, createdBy, runtime: getRuntime(), status: 'unavailable', screenshotDataUrl: null, createdAt: now, updatedAt: now, lastAction: 'create', // Consent is always OFF at creation — the user must explicitly grant control, // even for agent-initiated sessions controlling the full desktop. agentAccessEnabled: false, displaySize: null, message: null, cursor: null, }; const activeOwnerSessions = ownerSessions(ownerId).filter((item) => item.status === 'ready'); if (activeOwnerSessions.length >= MAX_SESSIONS_PER_OWNER) { throw new Error(`Computer Use is limited to ${MAX_SESSIONS_PER_OWNER} active session(s).`); } const settings = readSettings(); const readiness = getRuntimeReadiness(); const isCloud = getRuntime() === 'cloud'; const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled; const ready = isCloud ? desktopAgentRelay.isConnected() : settings.enabled && runtimeReady; if (!ready) { session.message = getSetupMessage(settings, readiness); sessions.set(session.id, session); return publicSession(session); } // In cloud mode the linked desktop agent is the consent authority and prompts // the user per its own consent mode, so the relay is allowed to act. In local // mode the user must still grant control from the panel. if (isCloud) { session.agentAccessEnabled = true; } session.status = 'ready'; session.message = isCloud ? 'Computer Use session is ready on the linked desktop.' : 'Computer Use session is ready. Grant control to let agents act.'; sessions.set(session.id, session); try { await refreshScreenshot(session); } catch (error) { session.status = 'unavailable'; session.message = error instanceof Error ? error.message : 'Failed to capture the screen.'; } return publicSession(session); }, async grantAgentAccess(owner: ComputerUseOwner, sessionId: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } session.agentAccessEnabled = true; session.updatedAt = new Date().toISOString(); session.lastAction = 'consent:grant'; return publicSession(session); }, async revokeAgentAccess(owner: ComputerUseOwner, sessionId: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } session.agentAccessEnabled = false; session.updatedAt = new Date().toISOString(); session.lastAction = 'consent:revoke'; return publicSession(session); }, async stopSession(owner: ComputerUseOwner, sessionId: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { return { stopped: false }; } session.status = 'stopped'; session.agentAccessEnabled = false; session.updatedAt = new Date().toISOString(); session.lastAction = 'stop'; session.message = 'Computer Use session stopped. Agent control is revoked.'; if (getRuntime() === 'cloud' && desktopAgentRelay.isConnected()) { // Best-effort: tell the desktop agent to forget this session's consent. void desktopAgentRelay.relay('stop_session', { sessionId }).catch(() => undefined); } return { stopped: true, session: publicSession(session) }; }, async deleteSession(owner: ComputerUseOwner, sessionId: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { return { deleted: false }; } sessions.delete(sessionId); return { deleted: true, sessionId }; }, // --- User-initiated actions (from the panel) ------------------------------- async userScreenshot(owner: ComputerUseOwner, sessionId: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } assertReady(session); await refreshScreenshot(session); session.lastAction = 'screenshot'; return publicSession(session); }, async userClick(owner: ComputerUseOwner, sessionId: string, input: { x: number; y: number; button?: ClickButton; double?: boolean }) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } assertReady(session); await performAction(session, { type: 'click', button: input.button || 'left', point: { x: input.x, y: input.y }, double: input.double === true, }); session.cursor = { x: input.x, y: input.y, actor: 'user' }; session.lastAction = input.double ? 'double_click' : 'click'; return publicSession(session); }, async userType(owner: ComputerUseOwner, sessionId: string, text: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } assertReady(session); await performAction(session, { type: 'type', text }); session.lastAction = 'type'; return publicSession(session); }, async userPressKey(owner: ComputerUseOwner, sessionId: string, key: string) { const ownerId = getOwnerId(owner); const session = sessions.get(sessionId); if (!session || !canAccessSession(ownerId, session)) { throw new Error('Computer Use session not found.'); } assertReady(session); await performAction(session, { type: 'key', key }); session.lastAction = `key:${key}`; return publicSession(session); }, // --- Agent-initiated actions (via MCP) ------------------------------------ async createAgentSession() { assertAgentToolsAvailable(); return this.createSession({ id: AGENT_OWNER_ID }, { createdBy: 'agent' }); }, async listAgentSessions() { if (!agentToolsAvailable()) { return []; } await expireStaleSessions(); return [...sessions.values()].map(publicSession); }, /** * Resolves a session the agent is allowed to act on. In local mode this * enforces the in-process per-session consent flag. In cloud mode the linked * desktop agent is the consent authority (it prompts the user per its own * consent mode), so this only requires the relay to be connected. */ async getConsentedSession(sessionId: string): Promise { assertAgentToolsAvailable(); const session = sessions.get(sessionId); if (!session) { throw new Error('Computer Use session not found.'); } if (getRuntime() !== 'cloud' && !session.agentAccessEnabled) { throw new Error('Computer Use session is awaiting user consent. Ask the user to grant control in the Computer panel.'); } assertReady(session); return session; }, async agentScreenshot(sessionId: string) { const session = await this.getConsentedSession(sessionId); await refreshScreenshot(session); session.lastAction = 'screenshot'; return publicSession(session); }, async agentCursorPosition(sessionId: string) { const session = await this.getConsentedSession(sessionId); const point = await getCursorPosition(session); session.cursor = { ...point, actor: 'agent' }; session.lastAction = 'cursor_position'; return { session: publicSession(session), position: point }; }, async agentMouseMove(sessionId: string, point: Point) { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'mouse_move', point }); session.cursor = { ...point, actor: 'agent' }; session.lastAction = 'mouse_move'; return publicSession(session); }, async agentClick(sessionId: string, button: ClickButton, point?: Point, doubleClick = false) { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'click', button, point, double: doubleClick }); if (point) { session.cursor = { ...point, actor: 'agent' }; } session.lastAction = doubleClick ? 'double_click' : `${button}_click`; return publicSession(session); }, async agentDrag(sessionId: string, from: Point, to: Point, button: ClickButton = 'left') { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'drag', from, to, button }); session.cursor = { ...to, actor: 'agent' }; session.lastAction = 'left_click_drag'; return publicSession(session); }, async agentType(sessionId: string, text: string) { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'type', text }); session.lastAction = 'type'; return publicSession(session); }, async agentKey(sessionId: string, key: string) { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'key', key }); session.lastAction = `key:${key}`; return publicSession(session); }, async agentScroll(sessionId: string, input: { direction: ScrollDirection; amount?: number; x?: number; y?: number }) { const session = await this.getConsentedSession(sessionId); const point = typeof input.x === 'number' && typeof input.y === 'number' ? { x: input.x, y: input.y } : undefined; await performAction(session, { type: 'scroll', direction: input.direction, amount: input.amount, point }); if (point) { session.cursor = { ...point, actor: 'agent' }; } session.lastAction = `scroll:${input.direction}`; return publicSession(session); }, async agentWait(sessionId: string, timeoutMs?: number) { const session = await this.getConsentedSession(sessionId); await performAction(session, { type: 'wait', ms: timeoutMs }); session.lastAction = 'wait'; return publicSession(session); }, async agentStopSession(sessionId: string) { assertAgentToolsAvailable(); return this.stopSession({ id: AGENT_OWNER_ID }, sessionId); }, /** * 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. */ async onDesktopAgentConnected() { if (getRuntime() !== 'cloud') { return; } try { await this.registerAgentMcp(); } catch (error) { console.warn('[Computer Use] Failed to register MCP for linked desktop agent:', error instanceof Error ? error.message : error); } }, /** Cloud only: tear down sessions when the last desktop agent disconnects. */ async onDesktopAgentDisconnected() { if (getRuntime() !== 'cloud' || desktopAgentRelay.isConnected()) { return; } for (const session of sessions.values()) { if (session.status === 'ready') { session.status = 'stopped'; session.agentAccessEnabled = false; session.updatedAt = new Date().toISOString(); session.lastAction = 'agent-disconnected'; session.message = 'The linked desktop agent disconnected.'; } } }, 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.'; } }, }; // Drive cloud MCP exposure + session teardown off desktop-agent connectivity. desktopAgentRelay.setHooks({ onFirstConnect: () => computerUseService.onDesktopAgentConnected(), onLastDisconnect: () => computerUseService.onDesktopAgentDisconnected(), }); process.once('beforeExit', () => { void computerUseService.stopAllSessions(); });