From 26a9fce7ba9f0216bad470d00c0195e6eabc15a2 Mon Sep 17 00:00:00 2001 From: milind-soni Date: Fri, 14 Aug 2026 01:01:22 +0530 Subject: [PATCH] =?UTF-8?q?Give=20ACP=20bots=20their=20computer=20?= =?UTF-8?q?=E2=80=94=20grok/gemini/antigravity=20could=20not=20touch=20the?= =?UTF-8?q?=20VM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bot on an ACP engine was told it had a cloud computer but had no way to use one: the driver mounted only the peer-comms MCP server, so screenshot, click and the rest never existed for it. Asked to drive its VM, the bot does the only thing it can — searches for tools, then calls list_bots hunting for a teammate who has them. The ACP session now mounts the same computer proxy the claude driver mounts, under the same "computer" name, with the box id and token in its env; the local (this Mac) path is mounted the same way. The driver declares computerMcp, so the harness attaches the box and the prompt sentence only for engines that can really act. Co-Authored-By: Claude Fable 5 --- server/drivers/acp/core.ts | 41 +++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/server/drivers/acp/core.ts b/server/drivers/acp/core.ts index 7deb7279e..253adb919 100644 --- a/server/drivers/acp/core.ts +++ b/server/drivers/acp/core.ts @@ -13,7 +13,10 @@ // is never a security contract). session/load REPLAYS history as ordinary // session/update notifications, so updates are double-gated: nothing emits // before the prompt is sent, and `_meta.isReplay` updates are dropped. +import { existsSync } from "node:fs"; import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { execCli, killCliTree, spawnCli } from "../../procs.ts"; @@ -28,6 +31,13 @@ import type { } from "../../contracts.ts"; import { newEventId, newId } from "../../contracts.ts"; import { augmentedPath } from "../../env-path.ts"; + +// the computer proxy entry: .ts in dev (node type stripping), .js in the +// compiled dist-server the packaged app ships +const COMPUTER_PROXY_PATH = (() => { + const ts = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "computer-proxy.ts"); + return existsSync(ts) ? ts : ts.replace(/\.ts$/, ".js"); +})(); import { appendNative } from "../native.ts"; export interface AcpConfig { @@ -131,13 +141,34 @@ export function createAcpDriver(support: AcpSupport): ProviderDriver // fine here. env is the ACP {name,value}[] shape. const acpMcpServers = (turn: SendTurnInput) => { const servers: Array<{ name: string; command: string; args: string[]; env: Array<{ name: string; value: string }> }> = []; + const acpEnv = (env: Record) => + Object.entries(env).map(([name, value]) => ({ name, value: String(value) })); const agents = turn.integrations?.agents; if (agents) { + servers.push({ name: "agents", command: agents.command, args: agents.args, env: acpEnv(agents.env) }); + } + // the bot's computer, mounted exactly like the claude driver does: + // an ACP agent gets the same screenshot/click/batch tools instead of + // being told it has a machine it cannot touch + const computer = turn.integrations?.computer; + if (computer) { + servers.push({ + name: "computer", + command: process.execPath, + args: [COMPUTER_PROXY_PATH], + env: acpEnv({ + ELECTRON_RUN_AS_NODE: "1", + OGB_BOX_ID: computer.boxId, + OGB_BOX_TOKEN: computer.token, + }), + }); + } else if (turn.integrations?.localComputer) { + const local = turn.integrations.localComputer; servers.push({ - name: "agents", - command: agents.command, - args: agents.args, - env: Object.entries(agents.env).map(([name, value]) => ({ name, value: String(value) })), + name: "computer", + command: local.command, + args: local.args, + env: acpEnv(local.env ?? {}), }); } return servers; @@ -478,7 +509,7 @@ export function createAcpDriver(support: AcpSupport): ProviderDriver snapshot, adapter: { provider: DRIVER_KIND, - capabilities: { sessionModelSwitch: "unsupported", agentsMcp: true }, + capabilities: { sessionModelSwitch: "unsupported", agentsMcp: true, computerMcp: true }, sendTurn, interruptTurn: async (threadId) => active.get(threadId)?.interrupt(), respondToRequest: async (threadId, requestId, decision) => {