feat: enable Computer Use with macOS + Windows + Linux support

Phase 1: Replace @ant/computer-use-mcp stub (12 files, 6517 lines).

Phase 2: Remove 8 macOS-only guards in src/:
- main.tsx: remove getPlatform()==='macos' check
- swiftLoader.ts: remove darwin-only throw
- executor.ts: extend platform guard, clipboard dispatch, paste key
- drainRunLoop.ts: skip CFRunLoop pump on non-darwin
- escHotkey.ts: non-darwin returns false (Ctrl+C fallback)
- hostAdapter.ts: non-darwin permissions granted
- common.ts: dynamic platform + screenshotFiltering
- gates.ts: enabled:true, subscription check removed

Phase 3: Add Linux backends (xdotool/scrot/xrandr/wmctrl):
- computer-use-input/backends/linux.ts (173 lines)
- computer-use-swift/backends/linux.ts (278 lines)

Verified on Windows x64: mouse, screenshot, displays, foreground app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
unraid
2026-04-03 22:33:00 +08:00
parent 465e9f01c6
commit e3264a1691
34 changed files with 8291 additions and 750 deletions

View File

@@ -68,6 +68,24 @@ function computeTargetDims(
}
async function readClipboardViaPbpaste(): Promise<string> {
if (process.platform === 'win32') {
const { stdout, code } = await execFileNoThrow('powershell', ['-NoProfile', '-Command', 'Get-Clipboard'], {
useCwd: false,
})
if (code !== 0) {
throw new Error(`PowerShell Get-Clipboard exited with code ${code}`)
}
return stdout
}
if (process.platform === 'linux') {
const { stdout, code } = await execFileNoThrow('xclip', ['-selection', 'clipboard', '-o'], {
useCwd: false,
})
if (code !== 0) {
throw new Error(`xclip exited with code ${code}`)
}
return stdout
}
const { stdout, code } = await execFileNoThrow('pbpaste', [], {
useCwd: false,
})
@@ -78,6 +96,25 @@ async function readClipboardViaPbpaste(): Promise<string> {
}
async function writeClipboardViaPbcopy(text: string): Promise<void> {
if (process.platform === 'win32') {
const { code } = await execFileNoThrow('powershell', ['-NoProfile', '-Command', `Set-Clipboard -Value '${text.replace(/'/g, "''")}'`], {
useCwd: false,
})
if (code !== 0) {
throw new Error(`PowerShell Set-Clipboard exited with code ${code}`)
}
return
}
if (process.platform === 'linux') {
const { code } = await execFileNoThrow('xclip', ['-selection', 'clipboard'], {
input: text,
useCwd: false,
})
if (code !== 0) {
throw new Error(`xclip exited with code ${code}`)
}
return
}
const { code } = await execFileNoThrow('pbcopy', [], {
input: text,
useCwd: false,
@@ -192,7 +229,7 @@ async function typeViaClipboard(input: Input, text: string): Promise<void> {
if ((await readClipboardViaPbpaste()) !== text) {
throw new Error('Clipboard write did not round-trip.')
}
await input.keys(['command', 'v'])
await input.keys([process.platform === 'darwin' ? 'command' : 'ctrl', 'v'])
await sleep(100)
} finally {
if (typeof saved === 'string') {
@@ -260,9 +297,9 @@ export function createCliExecutor(opts: {
getMouseAnimationEnabled: () => boolean
getHideBeforeActionEnabled: () => boolean
}): ComputerExecutor {
if (process.platform !== 'darwin') {
if (process.platform !== 'darwin' && process.platform !== 'win32' && process.platform !== 'linux') {
throw new Error(
`createCliExecutor called on ${process.platform}. Computer control is macOS-only.`,
`createCliExecutor called on ${process.platform}. Computer control requires macOS, Windows, or Linux.`,
)
}