/ utils / computerUse / hostAdapter.ts
hostAdapter.ts
 1  import type {
 2    ComputerUseHostAdapter,
 3    Logger,
 4  } from '@ant/computer-use-mcp/types'
 5  import { format } from 'util'
 6  import { logForDebugging } from '../debug.js'
 7  import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
 8  import { createCliExecutor } from './executor.js'
 9  import { getChicagoEnabled, getChicagoSubGates } from './gates.js'
10  import { requireComputerUseSwift } from './swiftLoader.js'
11  
12  class DebugLogger implements Logger {
13    silly(message: string, ...args: unknown[]): void {
14      logForDebugging(format(message, ...args), { level: 'debug' })
15    }
16    debug(message: string, ...args: unknown[]): void {
17      logForDebugging(format(message, ...args), { level: 'debug' })
18    }
19    info(message: string, ...args: unknown[]): void {
20      logForDebugging(format(message, ...args), { level: 'info' })
21    }
22    warn(message: string, ...args: unknown[]): void {
23      logForDebugging(format(message, ...args), { level: 'warn' })
24    }
25    error(message: string, ...args: unknown[]): void {
26      logForDebugging(format(message, ...args), { level: 'error' })
27    }
28  }
29  
30  let cached: ComputerUseHostAdapter | undefined
31  
32  /**
33   * Process-lifetime singleton. Built once on first CU tool call; native modules
34   * (both `@ant/computer-use-input` and `@ant/computer-use-swift`) are loaded
35   * here via the executor factory, which throws on load failure — there is no
36   * degraded mode.
37   */
38  export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
39    if (cached) return cached
40    cached = {
41      serverName: COMPUTER_USE_MCP_SERVER_NAME,
42      logger: new DebugLogger(),
43      executor: createCliExecutor({
44        getMouseAnimationEnabled: () => getChicagoSubGates().mouseAnimation,
45        getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
46      }),
47      ensureOsPermissions: async () => {
48        const cu = requireComputerUseSwift()
49        const accessibility = cu.tcc.checkAccessibility()
50        const screenRecording = cu.tcc.checkScreenRecording()
51        return accessibility && screenRecording
52          ? { granted: true }
53          : { granted: false, accessibility, screenRecording }
54      },
55      isDisabled: () => !getChicagoEnabled(),
56      getSubGates: getChicagoSubGates,
57      // cleanup.ts always unhides at turn end — no user preference to disable it.
58      getAutoUnhideEnabled: () => true,
59  
60      // Pixel-validation JPEG decode+crop. MUST be synchronous (the package
61      // does `patch1.equals(patch2)` directly on the return value). Cowork uses
62      // Electron's `nativeImage` (sync); our `image-processor-napi` is
63      // sharp-compatible and async-only. Returning null → validation skipped,
64      // click proceeds — the designed fallback per `PixelCompareResult.skipped`.
65      // The sub-gate defaults to false anyway.
66      cropRawPatch: () => null,
67    }
68    return cached
69  }