mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
119 lines
4.4 KiB
TypeScript
119 lines
4.4 KiB
TypeScript
import type {
|
|
ComputerUseHostAdapter,
|
|
Logger,
|
|
LoggerDetail,
|
|
} from '@ant/computer-use-mcp/types'
|
|
import { format } from 'util'
|
|
import { logForDebugging } from '../debug.js'
|
|
import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
|
|
import { createCliExecutor } from './executor.js'
|
|
import { getChicagoEnabled, getChicagoSubGates } from './gates.js'
|
|
import { requireComputerUseSwift } from './swiftLoader.js'
|
|
|
|
class DebugLogger implements Logger {
|
|
silly(message: string, detail?: LoggerDetail): void {
|
|
logForDebugging(format(message, detail ?? ''), { level: 'debug' })
|
|
}
|
|
debug(message: string, detail?: LoggerDetail): void {
|
|
logForDebugging(format(message, detail ?? ''), { level: 'debug' })
|
|
}
|
|
info(message: string, detail?: LoggerDetail): void {
|
|
logForDebugging(format(message, detail ?? ''), { level: 'info' })
|
|
}
|
|
warn(message: string, detail?: LoggerDetail): void {
|
|
logForDebugging(format(message, detail ?? ''), { level: 'warn' })
|
|
}
|
|
error(message: string, detail?: LoggerDetail): void {
|
|
logForDebugging(format(message, detail ?? ''), { level: 'error' })
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// JXA-based TCC permission probes (fallback when native .node module absent)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Probe accessibility by asking System Events for a process list. */
|
|
function checkAccessibilityJXA(): boolean {
|
|
try {
|
|
const result = Bun.spawnSync({
|
|
cmd: [
|
|
'osascript',
|
|
'-e',
|
|
'tell application "System Events" to get name of every process whose background only is false',
|
|
],
|
|
stdout: 'pipe',
|
|
stderr: 'pipe',
|
|
})
|
|
return result.exitCode === 0
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/** Probe screen recording by attempting a 1x1 screencapture. */
|
|
function checkScreenRecordingJXA(): boolean {
|
|
try {
|
|
const result = Bun.spawnSync({
|
|
cmd: ['screencapture', '-x', '-R', '0,0,1,1', '/dev/null'],
|
|
stdout: 'pipe',
|
|
stderr: 'pipe',
|
|
})
|
|
return result.exitCode === 0
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
let cached: ComputerUseHostAdapter | undefined
|
|
|
|
/**
|
|
* Process-lifetime singleton. Built once on first CU tool call; native modules
|
|
* (both `@ant/computer-use-input` and `@ant/computer-use-swift`) are loaded
|
|
* here via the executor factory, which throws on load failure — there is no
|
|
* degraded mode.
|
|
*/
|
|
export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
|
|
if (cached) return cached
|
|
cached = {
|
|
serverName: COMPUTER_USE_MCP_SERVER_NAME,
|
|
logger: new DebugLogger(),
|
|
executor: createCliExecutor({
|
|
getMouseAnimationEnabled: () => getChicagoSubGates().mouseAnimation,
|
|
getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
|
|
}),
|
|
ensureOsPermissions: async () => {
|
|
if (process.platform !== 'darwin') return { granted: true }
|
|
const cu = requireComputerUseSwift()
|
|
const tcc = (cu as any).tcc
|
|
// Native Swift .node module provides tcc.checkAccessibility/checkScreenRecording.
|
|
// When absent (decompiled/reverse-engineered build), fall back to JXA probes.
|
|
if (tcc) {
|
|
const accessibility = tcc.checkAccessibility()
|
|
const screenRecording = tcc.checkScreenRecording()
|
|
return accessibility && screenRecording
|
|
? { granted: true }
|
|
: { granted: false, accessibility, screenRecording }
|
|
}
|
|
// JXA fallback: try to query System Events (accessibility) and screencapture (screen recording).
|
|
const accessibility = checkAccessibilityJXA()
|
|
const screenRecording = checkScreenRecordingJXA()
|
|
return accessibility && screenRecording
|
|
? { granted: true }
|
|
: { granted: false, accessibility, screenRecording }
|
|
},
|
|
isDisabled: () => !getChicagoEnabled(),
|
|
getSubGates: getChicagoSubGates,
|
|
// cleanup.ts always unhides at turn end — no user preference to disable it.
|
|
getAutoUnhideEnabled: () => true,
|
|
|
|
// Pixel-validation JPEG decode+crop. MUST be synchronous (the package
|
|
// does `patch1.equals(patch2)` directly on the return value). Cowork uses
|
|
// Electron's `nativeImage` (sync); our `image-processor-napi` is
|
|
// sharp-compatible and async-only. Returning null → validation skipped,
|
|
// click proceeds — the designed fallback per `PixelCompareResult.skipped`.
|
|
// The sub-gate defaults to false anyway.
|
|
cropRawPatch: () => null,
|
|
}
|
|
return cached
|
|
}
|