mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-19 23:42:03 +08:00
Harden computer use MCP handling
This commit is contained in:
@@ -279,8 +279,7 @@ svg {
|
||||
.cc-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(6, 8, 11, 0.46);
|
||||
backdrop-filter: blur(16px);
|
||||
background: rgba(6, 8, 11, 0.28);
|
||||
display: none;
|
||||
z-index: 50;
|
||||
align-items: center;
|
||||
@@ -295,14 +294,14 @@ svg {
|
||||
.cc-sheet {
|
||||
width: 620px;
|
||||
max-width: min(92vw, 620px);
|
||||
max-height: 86vh;
|
||||
max-height: min(720px, 82vh);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 18px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--b);
|
||||
background: color-mix(in srgb, var(--s1) 94%, transparent);
|
||||
box-shadow: 0 32px 80px rgba(0, 0, 0, 0.32);
|
||||
background: color-mix(in srgb, var(--s1) 98%, transparent);
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
.cc-sheet-header {
|
||||
|
||||
@@ -8,8 +8,18 @@ function readBearerToken(header: unknown): string | null {
|
||||
if (typeof header !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
||||
return match?.[1] || null;
|
||||
const trimmed = header.trim();
|
||||
const scheme = 'Bearer';
|
||||
if (trimmed.slice(0, scheme.length).toLowerCase() !== scheme.toLowerCase()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const separator = trimmed[scheme.length];
|
||||
if (separator !== ' ' && separator !== '\t') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed.slice(scheme.length + 1).trimStart() || null;
|
||||
}
|
||||
|
||||
function toButton(value: unknown): 'left' | 'right' | 'middle' {
|
||||
|
||||
@@ -22,6 +22,8 @@ const MAX_SESSIONS_PER_OWNER = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE
|
||||
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';
|
||||
const DEFAULT_AGENT_WAIT_MS = 1000;
|
||||
const MAX_AGENT_WAIT_MS = 10_000;
|
||||
|
||||
type ComputerUseRuntime = 'cloud' | 'local';
|
||||
type ComputerUseSessionStatus = 'ready' | 'stopped' | 'unavailable';
|
||||
@@ -331,6 +333,16 @@ function applyRelayResult(session: ComputerUseSession, result: RelayResult): voi
|
||||
session.updatedAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
function normalizeAgentWaitMs(ms: number | undefined): number {
|
||||
if (ms === undefined) {
|
||||
return DEFAULT_AGENT_WAIT_MS;
|
||||
}
|
||||
if (!Number.isFinite(ms)) {
|
||||
throw new Error('Computer Use wait duration must be a finite number.');
|
||||
}
|
||||
return Math.trunc(Math.max(0, Math.min(ms, MAX_AGENT_WAIT_MS)));
|
||||
}
|
||||
|
||||
async function refreshScreenshot(session: ComputerUseSession): Promise<void> {
|
||||
if (getRuntime() === 'cloud') {
|
||||
const result = (await desktopAgentRelay.relay('screenshot', { sessionId: session.id })) as RelayResult;
|
||||
@@ -379,7 +391,7 @@ async function performAction(session: ComputerUseSession, action: ComputerAction
|
||||
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))));
|
||||
await new Promise((resolve) => setTimeout(resolve, normalizeAgentWaitMs(action.ms)));
|
||||
break;
|
||||
}
|
||||
await refreshScreenshot(session);
|
||||
@@ -437,12 +449,13 @@ export const computerUseService = {
|
||||
|
||||
async updateSettings(settings: Partial<ComputerUseSettings>) {
|
||||
const current = readSettings();
|
||||
const enabled = typeof settings.enabled === 'boolean' ? settings.enabled : current.enabled;
|
||||
const nextSettings = {
|
||||
...current,
|
||||
enabled: typeof settings.enabled === 'boolean' ? settings.enabled : current.enabled,
|
||||
enabled,
|
||||
agentToolsEnabled: typeof settings.agentToolsEnabled === 'boolean'
|
||||
? settings.agentToolsEnabled
|
||||
: current.agentToolsEnabled,
|
||||
: enabled,
|
||||
};
|
||||
if (!nextSettings.enabled) {
|
||||
nextSettings.agentToolsEnabled = false;
|
||||
|
||||
@@ -111,28 +111,16 @@ export default function ComputerUseSettingsTab() {
|
||||
|
||||
<SettingsRow
|
||||
label="Enable Computer Use"
|
||||
description="Allow CloudCLI to capture the screen and create desktop control sessions on this machine."
|
||||
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 })}
|
||||
onChange={(value) => void updateSettings({ enabled: value, agentToolsEnabled: value })}
|
||||
ariaLabel="Enable Computer Use"
|
||||
disabled={isLoading || isSaving}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
label="Enable Computer Tools for Agents"
|
||||
description="Register the Computer Use MCP server for all agent providers. Agents can request desktop control, but actions require your explicit per-session consent."
|
||||
>
|
||||
<SettingsToggle
|
||||
checked={settings.agentToolsEnabled}
|
||||
onChange={(value) => void updateSettings({ agentToolsEnabled: value })}
|
||||
ariaLabel="Enable Computer Tools for Agents"
|
||||
disabled={isLoading || isSaving || !settings.enabled}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
{(needsRuntime || isCloud || error) && (
|
||||
<div className="space-y-4 px-4 py-4">
|
||||
{isCloud && (
|
||||
|
||||
Reference in New Issue
Block a user