export declare const SERVER_MJS_SOURCE = "import { setTimeout as delay } from \"node:timers/promises\";\nimport { execFile, execFileSync } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { chmod, rename, rm } from \"node:fs/promises\";\nimport { promisify } from \"node:util\";\nimport http from \"node:http\";\nimport { env } from \"node:process\";\nimport { dirname, resolve } from \"node:path\";\n\nimport { CuaDriver } from \"@trycua/cua-driver\";\n\nimport { pointerMotionPlan } from \"./pointer-motion.mjs\";\nimport { selectGroundingElements } from \"./state.mjs\";\nimport { naturalTypingPlan } from \"./typing.mjs\";\n\nconst socketPath = env.COMPUTER_USE_SOCKET_PATH;\nconst workspaceRoot = env.COMPUTER_USE_ROOT;\nconst maxRequestBytes = 32 * 1024;\nconst maxRequestMs = 120_000;\nconst maxRecordingMs = 300_000;\nconst nativeCallTimeoutMs = 10_000;\n// Only the queue owner sets this signal; recording finalization deliberately bypasses it.\nlet requestSignal;\nconst desktopMargin = 24;\nconst minimumWindow = { height: 240, width: 480 };\nconst layoutTolerance = { height: 8, width: 8, x: 4, y: 4 };\nconst maxClipboardBytes = 10_000;\nconst driver = await CuaDriver.create({ claudeCodeCompatibility: false });\nconst execFilePromise = promisify(execFile);\nfunction execFileAsync(file, args, options) {\n return execFilePromise(file, args, {\n timeout: nativeCallTimeoutMs,\n ...options,\n signal: requestSignal,\n });\n}\n\nlet activeRecording = null;\nlet queue = Promise.resolve();\nlet shuttingDown = false;\n\nawait rm(socketPath, { force: true });\n\nconst server = http.createServer((request, response) => {\n if (shuttingDown) return response.destroy();\n if (request.method !== \"POST\" || request.url !== \"/call\") {\n sendJson(response, 404, { error: \"not found\" });\n return;\n }\n const controller = new AbortController();\n const deadline = Number(request.headers[\"x-computer-use-deadline\"]);\n const timeoutMs = Number.isFinite(deadline)\n ? Math.max(0, Math.min(maxRequestMs, deadline - Date.now()))\n : maxRequestMs;\n const cancel = () =>\n controller.abort(new Error(\"computer-use request cancelled or deadline exceeded\"));\n const timer = setTimeout(cancel, timeoutMs);\n response.once(\"close\", cancel);\n controller.signal.addEventListener(\"abort\", () => response.destroy(), { once: true });\n if (timeoutMs === 0) cancel();\n // Read immediately: a body may finish or disconnect while another call owns the queue.\n const body = readBody(request);\n void body.catch(() => undefined);\n const task = queue.then(async () => {\n controller.signal.throwIfAborted();\n if (shuttingDown) return;\n requestSignal = controller.signal;\n try {\n await handleRequest(await body, response);\n } finally {\n requestSignal = undefined;\n }\n });\n queue = task\n .catch(() => response.destroy())\n .finally(() => {\n clearTimeout(timer);\n response.removeListener(\"close\", cancel);\n });\n});\n\nserver.listen(socketPath, () => {\n void chmod(socketPath, 0o600);\n});\n\nfor (const signal of [\"SIGINT\", \"SIGTERM\"]) {\n process.once(signal, () => {\n void shutdown();\n });\n}\n\nasync function shutdown() {\n if (shuttingDown) return;\n shuttingDown = true;\n server.close();\n server.closeAllConnections();\n const timer = setTimeout(() => process.exit(1), nativeCallTimeoutMs * 2);\n let exitCode = 0;\n try {\n await queue;\n if (activeRecording !== null) await stopRecording();\n } catch {\n exitCode = 1;\n } finally {\n try {\n await driver.shutdown();\n } catch {\n exitCode = 1;\n } finally {\n clearTimeout(timer);\n process.exit(exitCode);\n }\n }\n}\n\nasync function handleRequest(body, response) {\n try {\n const input = JSON.parse(body);\n if (input.action === \"health\") {\n sendJson(response, 200, { backend: \"cua-driver\", version: \"0.12.5\" });\n return;\n }\n\n validateRequest(input);\n const actionResult = await performAction(input.action);\n const state = await captureState(input.screenshotPath);\n sendJson(response, 200, {\n action: actionResult,\n backend: \"cua-driver\",\n screenshot: input.screenshotPath,\n state,\n version: \"0.12.5\",\n });\n } catch (error) {\n if (requestSignal?.aborted && activeRecording !== null) {\n await stopRecording().catch(() => undefined);\n }\n sendJson(response, 500, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nasync function performAction(action) {\n requestSignal?.throwIfAborted();\n validateActionCoordinates(action);\n if (action.action === \"sequence\") {\n const results = [];\n try {\n for (const nested of action.actions) {\n const startedAt = performance.now();\n const result = await performAction(nested);\n results.push({\n action: nested.action,\n durationMs: Math.round(performance.now() - startedAt),\n result,\n });\n }\n return results;\n } catch (error) {\n // Never strand the durable driver in an active-recording state when a\n // rehearsed step fails. Preserve the original action error.\n if (activeRecording !== null) await stopRecording().catch(() => undefined);\n throw error;\n }\n }\n switch (action.action) {\n case \"screenshot\":\n return null;\n case \"click\":\n await moveCursor(action);\n return click(action, 1);\n case \"double_click\":\n await moveCursor(action);\n return click(action, 2);\n case \"triple_click\":\n await moveCursor(action);\n return click(action, 3);\n case \"drag\":\n return dragCursor(action);\n case \"keypress\":\n return action.keys.length === 1\n ? call(\"press_key\", {\n scope: \"desktop\",\n key: normalizeKey(action.keys[0]),\n })\n : call(\"hotkey\", {\n scope: \"desktop\",\n keys: action.keys.map(normalizeKey),\n });\n case \"move\":\n return moveCursor(action);\n case \"scroll\": {\n await call(\"move_cursor\", {\n scope: \"desktop\",\n x: action.x,\n y: action.y,\n });\n const results = [];\n if (action.scrollY !== 0) {\n results.push(await call(\"scroll\", scrollInput(action.scrollY, \"up\", \"down\")));\n }\n if (action.scrollX !== 0) {\n results.push(await call(\"scroll\", scrollInput(action.scrollX, \"left\", \"right\")));\n }\n return results;\n }\n case \"type\":\n return typeText(action);\n case \"clipboard_read\":\n return readClipboard(action.selection);\n case \"clipboard_write\":\n return writeClipboard(action.selection, action.text);\n case \"wait\":\n await sleep(action.durationMs ?? 2_000);\n return null;\n case \"arrange_windows\":\n return arrangeWindows(action);\n case \"record_start\":\n return startRecording(action.path);\n case \"record_stop\":\n return stopRecording();\n default:\n throw new Error(`unsupported computer-use action: ${String(action.action)}`);\n }\n}\n\nasync function captureState(screenshotPath) {\n await call(\"get_desktop_state\", { screenshot_out_file: screenshotPath });\n const pointer = structured(await call(\"get_cursor_position\", {}));\n const screen = x11ScreenSize();\n const windows = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n const contentWindows = Array.isArray(windows) ? windows.filter(isContentWindow) : [];\n const groundingWindow = contentWindows.toSorted((left, right) => right.z_index - left.z_index)[0];\n\n let elements = [];\n if (groundingWindow !== undefined) {\n const windowState = structured(\n await call(\"get_window_state\", {\n pid: groundingWindow.pid,\n window_id: groundingWindow.window_id,\n include_screenshot: false,\n max_depth: 20,\n max_elements: 250,\n }),\n );\n if (Array.isArray(windowState.elements)) {\n elements = selectGroundingElements(windowState.elements, {\n width: Number(screen.width) || 1920,\n height: Number(screen.height) || 1080,\n });\n }\n }\n\n return formatState(groundingWindow, pointer, elements);\n}\n\nasync function startRecording(path) {\n if (activeRecording !== null) {\n throw new Error(\"a recording is already active\");\n }\n\n const outputDirectory = `${workspaceRoot}/recording-${randomUUID()}`;\n const result = await call(\n \"start_recording\",\n {\n output_dir: outputDirectory,\n record_video: true,\n },\n false,\n );\n activeRecording = {\n outputDirectory,\n outputPath: path,\n stopping: null,\n timer: setTimeout(() => {\n void stopRecording().catch(() => {\n void shutdown();\n });\n }, maxRecordingMs),\n };\n return result;\n}\n\nasync function stopRecording() {\n if (activeRecording === null) {\n throw new Error(\"no recording is active\");\n }\n\n const recording = activeRecording;\n if (recording.stopping !== null) return recording.stopping;\n clearTimeout(recording.timer);\n recording.stopping = finalizeRecording(recording);\n return recording.stopping;\n}\n\nasync function finalizeRecording(recording) {\n try {\n const result = await call(\"stop_recording\", {}, false);\n const output = structured(result);\n const videoPath =\n typeof output.last_video_path === \"string\"\n ? output.last_video_path\n : recording.outputDirectory + \"/recording.mp4\";\n await rename(videoPath, recording.outputPath);\n await rm(recording.outputDirectory, { force: true, recursive: true });\n return result;\n } catch (error) {\n void shutdown();\n throw error;\n } finally {\n activeRecording = null;\n }\n}\n\nasync function call(name, input, cancellable = true) {\n const signal = cancellable ? requestSignal : undefined;\n signal?.throwIfAborted();\n let timer;\n const result = await Promise.race([\n driver.callTool(name, JSON.stringify(input)),\n new Promise((_, reject) => {\n timer = setTimeout(() => {\n // Native calls have no abort API. Do not reuse a driver with uncertain in-flight work.\n void shutdown();\n reject(new Error(\"computer-use native call timed out; restart with startComputerUse\"));\n }, nativeCallTimeoutMs);\n }),\n ]).finally(() => clearTimeout(timer));\n signal?.throwIfAborted();\n if (result.isError) {\n throw new Error(\n `Cua ${name} failed${result.errorCode ? ` (${result.errorCode})` : \"\"}: ${result.text}`,\n );\n }\n return {\n degraded: result.degraded,\n structuredJson: result.structuredJson,\n text: result.text,\n verified: result.verified,\n };\n}\n\nfunction structured(result) {\n if (result.structuredJson === undefined) return {};\n return JSON.parse(result.structuredJson);\n}\n\nfunction pointerClickInput(x, y, button, count) {\n return {\n scope: \"desktop\",\n x,\n y,\n button: button === \"wheel\" ? \"middle\" : button,\n count,\n };\n}\n\nasync function moveCursor(action) {\n if (action.movementStyle === undefined) {\n return call(\"move_cursor\", { scope: \"desktop\", x: action.x, y: action.y });\n }\n const current = structured(await call(\"get_cursor_position\", {}));\n const start = { x: Number(current.x), y: Number(current.y) };\n if (!Number.isFinite(start.x) || !Number.isFinite(start.y)) {\n return call(\"move_cursor\", { scope: \"desktop\", x: action.x, y: action.y });\n }\n const options = { style: action.movementStyle };\n if (action.durationMs !== undefined) options.durationMs = action.durationMs;\n return followPointerPlan(pointerMotionPlan(start, { x: action.x, y: action.y }, options), \"x11\");\n}\n\nasync function dragCursor(action) {\n const start = action.path[0];\n const end = action.path.at(-1);\n if (action.movementStyle === undefined) {\n return call(\"drag\", {\n scope: \"desktop\",\n from_x: start.x,\n from_y: start.y,\n to_x: end.x,\n to_y: end.y,\n steps: Math.min(200, Math.max(20, action.path.length * 10)),\n });\n }\n const style = action.movementStyle;\n const current = structured(await call(\"get_cursor_position\", {}));\n const currentPoint = { x: Number(current.x), y: Number(current.y) };\n const approach =\n Number.isFinite(currentPoint.x) && Number.isFinite(currentPoint.y)\n ? pointerPlanArgs(pointerMotionPlan(currentPoint, start, { style: \"natural\" }))\n : [\"mousemove\", String(start.x), String(start.y)];\n const options = { style };\n if (action.durationMs !== undefined) options.durationMs = action.durationMs;\n const drag = pointerPlanArgs(pointerMotionPlan(start, end, options));\n return xdotool([\n ...approach,\n \"sleep\",\n \"0.025\",\n \"mousedown\",\n \"1\",\n \"sleep\",\n \"0.030\",\n ...drag,\n \"sleep\",\n \"0.030\",\n \"mouseup\",\n \"1\",\n ]);\n}\n\nasync function followPointerPlan(plan, backend = \"cua\") {\n if (backend === \"x11\") return xdotool(pointerPlanArgs(plan));\n const results = [];\n for (let phaseIndex = 0; phaseIndex < plan.phases.length; phaseIndex += 1) {\n for (const point of plan.phases[phaseIndex]) {\n results.push(await call(\"move_cursor\", { scope: \"desktop\", x: point.x, y: point.y }));\n if (point.delayMs > 0) await sleep(point.delayMs);\n }\n if (phaseIndex < plan.phases.length - 1 && plan.settleMs > 0) await sleep(plan.settleMs);\n }\n if (plan.phases.length === 1 && plan.settleMs > 0) await sleep(plan.settleMs);\n return results;\n}\n\nfunction pointerPlanArgs(plan) {\n const args = [];\n for (let phaseIndex = 0; phaseIndex < plan.phases.length; phaseIndex += 1) {\n for (const point of plan.phases[phaseIndex]) {\n args.push(\"mousemove\", String(point.x), String(point.y));\n if (point.delayMs > 0) args.push(\"sleep\", (point.delayMs / 1000).toFixed(3));\n }\n if (phaseIndex < plan.phases.length - 1 && plan.settleMs > 0) {\n args.push(\"sleep\", (plan.settleMs / 1000).toFixed(3));\n }\n }\n if (plan.phases.length === 1 && plan.settleMs > 0) {\n args.push(\"sleep\", (plan.settleMs / 1000).toFixed(3));\n }\n return args;\n}\n\nasync function xdotool(args) {\n const plannedSleepMs = args.reduce(\n (total, value, index) => (args[index - 1] === \"sleep\" ? total + Number(value) * 1000 : total),\n 0,\n );\n const timeout = Math.max(2_000, Math.ceil(plannedSleepMs + 1_500));\n const result = await execFileAsync(\"xdotool\", args, { env: displayEnv(), timeout });\n return {\n plannedSleepMs: Math.round(plannedSleepMs),\n stderr: result.stderr,\n stdout: result.stdout,\n };\n}\n\nfunction typingPlan(action) {\n if (action.typingStyle === \"natural\") return naturalTypingPlan(action.text);\n return [...new Intl.Segmenter().segment(action.text)].map(({ segment }) => ({\n grapheme: segment,\n delayMs: action.typingDelayMs ?? 0,\n }));\n}\n\nfunction validateTypingBudget(actions) {\n let characters = 0;\n let calls = 0;\n let delayMs = 0;\n for (const action of actions) {\n if (action.action !== \"type\") continue;\n if (typeof action.text !== \"string\" || action.text.length > 10_000) {\n throw new Error(\"typing text must be at most 10000 characters\");\n }\n characters += action.text.length;\n if (action.typingStyle === undefined && !action.typingDelayMs) continue;\n const plan = typingPlan(action);\n calls += plan.length;\n delayMs += plan.reduce((total, step) => total + step.delayMs, 0);\n }\n if (characters > 10_000 || calls > 1_000 || delayMs > 30_000) {\n throw new Error(\n \"typing budget exceeded: at most 10000 characters, 1000 paced graphemes and 30000ms delay per request; use unpaced typing for longer text\",\n );\n }\n}\n\nasync function typeText(action) {\n if (action.typingStyle === undefined && !action.typingDelayMs) {\n return call(\"type_text\", { scope: \"desktop\", text: action.text });\n }\n let result;\n let graphemesTyped = 0;\n for (const { grapheme, delayMs } of typingPlan(action)) {\n result = await call(\"type_text\", { scope: \"desktop\", text: grapheme });\n graphemesTyped += 1;\n if (delayMs > 0) await sleep(delayMs);\n }\n return { graphemesTyped, result };\n}\n\nasync function click(action, count) {\n if (action.button !== \"back\" && action.button !== \"forward\") {\n return call(\"click\", pointerClickInput(action.x, action.y, action.button, count));\n }\n\n await call(\"move_cursor\", {\n scope: \"desktop\",\n x: action.x,\n y: action.y,\n });\n const results = [];\n for (let index = 0; index < count; index += 1) {\n results.push(\n await call(\"hotkey\", {\n scope: \"desktop\",\n keys: [\"alt\", action.button === \"back\" ? \"left\" : \"right\"],\n }),\n );\n }\n return results;\n}\n\nasync function readClipboard(selection) {\n try {\n const result = await execFileAsync(\"xclip\", [\"-selection\", xclipSelection(selection), \"-out\"], {\n env: displayEnv(),\n maxBuffer: maxClipboardBytes + 1,\n });\n if (Buffer.byteLength(result.stdout) > maxClipboardBytes) {\n throw new Error(`clipboard content exceeds ${maxClipboardBytes} bytes`);\n }\n return { selection, text: result.stdout };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"clipboard content exceeds\")) throw error;\n return { selection, text: \"\" };\n }\n}\n\nasync function writeClipboard(selection, text) {\n if (Buffer.byteLength(text) > maxClipboardBytes) {\n throw new Error(`clipboard content exceeds ${maxClipboardBytes} bytes`);\n }\n await new Promise((resolve, reject) => {\n const child = execFile(\"xclip\", [\"-selection\", xclipSelection(selection), \"-in\"], {\n env: displayEnv(),\n timeout: nativeCallTimeoutMs,\n signal: requestSignal,\n });\n child.once(\"error\", reject);\n child.once(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`xclip exited ${code}`)),\n );\n child.stdin.end(text);\n });\n return { bytes: Buffer.byteLength(text), selection };\n}\n\nfunction xclipSelection(selection) {\n return selection === \"primary\" ? \"primary\" : \"clipboard\";\n}\n\nasync function arrangeWindows(action) {\n const scene = validateWindowScene(action.windows);\n const deadline = Date.now() + action.waitForMs;\n let resolved = null;\n while (resolved === null) {\n const windows = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n resolved = resolveSceneWindows(scene, Array.isArray(windows) ? windows : []);\n if (resolved !== null) break;\n if (Date.now() >= deadline) {\n throw new Error(\n `arrange_windows timed out waiting for: ${missingSceneSelectors(scene, Array.isArray(windows) ? windows : []).join(\", \")}`,\n );\n }\n await sleep(100);\n }\n\n // Resolve the complete scene before mutating any window. A missing selector\n // therefore leaves the existing desktop untouched.\n for (const item of resolved) {\n const windowId = String(item.window.window_id);\n await execFileAsync(\"xdotool\", [\"windowactivate\", \"--sync\", windowId], { env: displayEnv() });\n await execFileAsync(\n \"xdotool\",\n [\"windowmove\", windowId, String(item.bounds.x), String(item.bounds.y)],\n {\n env: displayEnv(),\n },\n );\n await execFileAsync(\n \"xdotool\",\n [\"windowsize\", windowId, String(item.bounds.width), String(item.bounds.height)],\n { env: displayEnv() },\n );\n }\n\n let verified = null;\n let windows = [];\n for (let attempt = 0; attempt < 10; attempt += 1) {\n const response = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n windows = Array.isArray(response) ? response : [];\n verified = resolveSceneWindows(scene, windows);\n if (\n verified !== null &&\n verified.every(({ bounds, window }) => approximatelyEqual(bounds, window))\n ) {\n return verified.map(layoutResult);\n }\n await sleep(100);\n }\n\n const screenshotPath = `${workspaceRoot}/layout-failure-${Date.now()}.png`;\n await call(\"get_desktop_state\", { screenshot_out_file: screenshotPath });\n const details =\n verified === null\n ? { stage: \"selector_resolution\", missing: missingSceneSelectors(scene, windows) }\n : {\n stage: \"verification\",\n tolerance: layoutTolerance,\n windows: verified.map(layoutResult),\n };\n throw new Error(\n `arrange_windows failed: ${JSON.stringify({ ...details, screenshot: screenshotPath })}`,\n );\n}\n\nfunction validateWindowScene(windows) {\n if (!Array.isArray(windows) || windows.length < 1 || windows.length > 4) {\n throw new Error(\"arrange_windows requires 1 to 4 windows\");\n }\n const scene = windows.map((item) => ({ ...item, bounds: regionBounds(item.region) }));\n const selectors = scene.map((item) => selectorLabel(item.selector));\n if (new Set(selectors).size !== selectors.length) {\n throw new Error(\"arrange_windows selectors must be unique\");\n }\n for (let left = 0; left < scene.length; left += 1) {\n for (let right = left + 1; right < scene.length; right += 1) {\n if (overlaps(scene[left].bounds, scene[right].bounds)) {\n throw new Error(\"arrange_windows regions must not overlap\");\n }\n }\n }\n return scene;\n}\n\nfunction desktopBounds() {\n const screen = x11ScreenSize();\n return {\n x: desktopMargin,\n y: desktopMargin,\n width: screen.width - desktopMargin * 2,\n height: screen.height - desktopMargin * 2,\n };\n}\n\nfunction x11ScreenSize() {\n // The X server is the source of truth after set_desktop_size. Avoid CUA's\n // session-oriented get_screen_size contract, which differs from pointer\n // actions and does not accept scope.\n const output = execFileSync(\"xdpyinfo\", [\"-display\", \":99\"], {\n encoding: \"utf8\",\n timeout: 2_000,\n });\n const match = /dimensions:\\s+(\\d+)x(\\d+) pixels/u.exec(output);\n if (match === null) throw new Error(\"could not determine desktop dimensions\");\n return { width: Number(match[1]), height: Number(match[2]) };\n}\n\nfunction regionBounds(region) {\n if (\n typeof region !== \"object\" ||\n region === null ||\n ![region.x, region.y, region.width, region.height].every(Number.isFinite) ||\n region.x < 0 ||\n region.y < 0 ||\n region.width <= 0 ||\n region.height <= 0 ||\n region.x + region.width > 1 ||\n region.y + region.height > 1\n ) {\n throw new Error(\"arrange_windows regions must fit within the desktop\");\n }\n const desktop = desktopBounds();\n const left = Math.round(desktop.x + desktop.width * region.x);\n const top = Math.round(desktop.y + desktop.height * region.y);\n const right = Math.round(desktop.x + desktop.width * (region.x + region.width));\n const bottom = Math.round(desktop.y + desktop.height * (region.y + region.height));\n const bounds = { x: left, y: top, width: right - left, height: bottom - top };\n if (bounds.width < minimumWindow.width || bounds.height < minimumWindow.height) {\n throw new Error(\n `arrange_windows regions must be at least ${minimumWindow.width}x${minimumWindow.height}`,\n );\n }\n return bounds;\n}\n\nfunction resolveSceneWindows(scene, windows) {\n const resolved = scene.map((item) => {\n const window = windows.find((candidate) => matchesSelector(candidate, item.selector));\n return window === undefined ? null : { bounds: item.bounds, window };\n });\n return resolved.includes(null) ? null : resolved;\n}\n\nfunction missingSceneSelectors(scene, windows) {\n return scene\n .filter((item) => !windows.some((window) => matchesSelector(window, item.selector)))\n .map((item) => selectorLabel(item.selector));\n}\n\nfunction matchesSelector(window, selector) {\n const identity = `${window.app_name ?? \"\"}\\n${window.title ?? \"\"}`.toLowerCase();\n if (selector.app === \"firefox\") return identity.includes(\"firefox\");\n const title = selector.instance === \"primary\" ? \"terminal 1\" : \"terminal 2\";\n return identity.includes(title);\n}\n\nfunction selectorLabel(selector) {\n return selector.app === \"firefox\" ? \"firefox\" : `xterm:${selector.instance}`;\n}\n\nfunction overlaps(left, right) {\n return (\n left.x < right.x + right.width &&\n left.x + left.width > right.x &&\n left.y < right.y + right.height &&\n left.y + left.height > right.y\n );\n}\n\nfunction layoutResult({ bounds, window }) {\n const observed = {\n x: Number(window.x),\n y: Number(window.y),\n width: Number(window.width),\n height: Number(window.height),\n };\n return {\n selector: `${window.app_name ?? \"\"}:${window.title ?? \"\"}`,\n requested: bounds,\n observed,\n delta: {\n x: observed.x - bounds.x,\n y: observed.y - bounds.y,\n width: observed.width - bounds.width,\n height: observed.height - bounds.height,\n },\n tolerance: layoutTolerance,\n title: window.title,\n window_id: window.window_id,\n };\n}\n\nfunction displayEnv() {\n return { ...env, DISPLAY: \":99\" };\n}\n\nfunction approximatelyEqual(bounds, window) {\n return (\n Math.abs(bounds.x - window.x) <= layoutTolerance.x &&\n Math.abs(bounds.y - window.y) <= layoutTolerance.y &&\n Math.abs(bounds.width - window.width) <= layoutTolerance.width &&\n Math.abs(bounds.height - window.height) <= layoutTolerance.height\n );\n}\n\nfunction sleep(milliseconds) {\n return delay(milliseconds, undefined, { signal: requestSignal });\n}\n\nfunction scrollInput(delta, negativeDirection, positiveDirection) {\n return {\n scope: \"desktop\",\n direction: delta < 0 ? negativeDirection : positiveDirection,\n amount: Math.min(50, Math.max(1, Math.round(Math.abs(delta) / 100))),\n by: \"line\",\n };\n}\n\nfunction normalizeKey(key) {\n const aliases = {\n ARROWDOWN: \"down\",\n ARROWLEFT: \"left\",\n ARROWRIGHT: \"right\",\n ARROWUP: \"up\",\n CMD: \"meta\",\n COMMAND: \"meta\",\n CONTROL: \"ctrl\",\n DEL: \"delete\",\n ENTER: \"enter\",\n ESC: \"escape\",\n OPTION: \"alt\",\n RETURN: \"enter\",\n };\n return aliases[key] ?? key.toLowerCase();\n}\n\nfunction isContentWindow(window) {\n const identity = `${window.app_name ?? \"\"}\\n${window.title ?? \"\"}`.toLowerCase();\n return (\n window.width >= 200 &&\n window.height >= 100 &&\n !identity.includes(\"xfce4-panel\") &&\n !identity.includes(\"xfdesktop\") &&\n window.title !== \"Desktop\"\n );\n}\n\nfunction formatState(groundingWindow, pointer, elements) {\n const lines = [\n groundingWindow === undefined\n ? \"grounding_window\\t(none)\"\n : `grounding_window\\t${groundingWindow.title} pid=${groundingWindow.pid} window_id=${groundingWindow.window_id}`,\n `pointer\\tx=${pointer.x ?? \"unknown\"} y=${pointer.y ?? \"unknown\"}`,\n \"accessibility_elements\",\n ];\n\n for (const element of elements) {\n const frame =\n element.frame === undefined\n ? \"\"\n : ` x=${element.frame.x} y=${element.frame.y} width=${element.frame.w} height=${element.frame.h}`;\n lines.push(\n `[${element.element_index}] role=${element.role}${frame} label=${JSON.stringify(element.label ?? \"\")}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\nfunction validateActionCoordinates(action) {\n const screen = x11ScreenSize();\n const points = [];\n if ([\"click\", \"double_click\", \"triple_click\", \"move\", \"scroll\"].includes(action.action)) {\n points.push({ x: action.x, y: action.y });\n }\n if (action.action === \"drag\") points.push(...action.path);\n for (const point of points) {\n if (\n !Number.isFinite(point.x) ||\n !Number.isFinite(point.y) ||\n point.x < 0 ||\n point.y < 0 ||\n point.x >= screen.width ||\n point.y >= screen.height\n ) {\n throw new Error(\n `computer-use coordinate is outside the ${screen.width}x${screen.height} desktop`,\n );\n }\n }\n}\n\nfunction validateRequest(input) {\n if (typeof input !== \"object\" || input === null) {\n throw new Error(\"request must be an object\");\n }\n if (!isArtifactPath(input.screenshotPath, \".png\")) {\n throw new Error(\"screenshot path must be a PNG under the computer-use workspace\");\n }\n if (typeof input.action !== \"object\" || input.action === null) {\n throw new Error(\"request action must be an object\");\n }\n const actions = new Set([\n \"arrange_windows\",\n \"click\",\n \"clipboard_read\",\n \"clipboard_write\",\n \"double_click\",\n \"drag\",\n \"keypress\",\n \"move\",\n \"record_start\",\n \"record_stop\",\n \"screenshot\",\n \"scroll\",\n \"sequence\",\n \"triple_click\",\n \"type\",\n \"wait\",\n ]);\n if (!actions.has(input.action.action)) {\n throw new Error(`unsupported computer-use action: ${String(input.action.action)}`);\n }\n if (input.action.action === \"sequence\") {\n if (\n !Array.isArray(input.action.actions) ||\n input.action.actions.length < 1 ||\n input.action.actions.length > 30\n ) {\n throw new Error(\"sequence must contain 1 to 30 actions\");\n }\n for (const action of input.action.actions) validateNestedSequenceAction(action);\n }\n if (input.action.action === \"arrange_windows\") validateArrangeWindowsAction(input.action);\n if (input.action.action === \"record_start\" && !isArtifactPath(input.action.path, \".mp4\")) {\n throw new Error(\"recording path must be an MP4 under the computer-use workspace\");\n }\n if (\n [\"click\", \"double_click\", \"triple_click\", \"drag\", \"move\"].includes(input.action.action) &&\n \"durationMs\" in input.action &&\n (!Number.isInteger(input.action.durationMs) ||\n input.action.durationMs < 0 ||\n input.action.durationMs > 2000)\n ) {\n throw new Error(\"pointer durationMs must be an integer from 0 to 2000\");\n }\n validateMovementStyle(input.action, \"\");\n if (\n input.action.action === \"wait\" &&\n (!Number.isInteger(input.action.durationMs) ||\n input.action.durationMs < 100 ||\n input.action.durationMs > 10_000)\n ) {\n throw new Error(\"wait durationMs must be an integer from 100 to 10000\");\n }\n validateClipboardOptions(input.action);\n validateTypingOptions(input.action, \"\");\n validateTypingBudget(input.action.action === \"sequence\" ? input.action.actions : [input.action]);\n}\n\nfunction validateNestedSequenceAction(action) {\n if (typeof action !== \"object\" || action === null)\n throw new Error(\"sequence action must be an object\");\n const allowed = new Set([\n \"arrange_windows\",\n \"click\",\n \"double_click\",\n \"drag\",\n \"keypress\",\n \"move\",\n \"record_start\",\n \"record_stop\",\n \"triple_click\",\n \"type\",\n \"wait\",\n ]);\n if (!allowed.has(action.action))\n throw new Error(`unsupported sequence action: ${String(action.action)}`);\n if (action.action === \"arrange_windows\") validateArrangeWindowsAction(action);\n if (action.action === \"record_start\" && !isArtifactPath(action.path, \".mp4\")) {\n throw new Error(\"sequence recording path must be an MP4 under the computer-use workspace\");\n }\n if (\n [\"click\", \"double_click\", \"triple_click\", \"drag\", \"move\"].includes(action.action) &&\n \"durationMs\" in action &&\n (!Number.isInteger(action.durationMs) || action.durationMs < 0 || action.durationMs > 2000)\n ) {\n throw new Error(\"sequence pointer durationMs must be an integer from 0 to 2000\");\n }\n validateMovementStyle(action, \"sequence \");\n if (\n action.action === \"wait\" &&\n (!Number.isInteger(action.durationMs) || action.durationMs < 100 || action.durationMs > 10_000)\n ) {\n throw new Error(\"sequence wait durationMs must be an integer from 100 to 10000\");\n }\n if (action.action === \"type\") validateTypingOptions(action, \"sequence \");\n}\n\nfunction validateMovementStyle(action, prefix) {\n if (\n \"movementStyle\" in action &&\n action.movementStyle !== \"natural\" &&\n action.movementStyle !== \"precision\"\n ) {\n throw new Error(`${prefix}movementStyle must be natural or precision`);\n }\n if (\n action.action === \"drag\" &&\n \"movementStyle\" in action &&\n action.movementStyle !== \"precision\"\n ) {\n throw new Error(`${prefix}drag movementStyle must be precision`);\n }\n}\n\nfunction validateClipboardOptions(action) {\n if (\n [\"clipboard_read\", \"clipboard_write\"].includes(action.action) &&\n action.selection !== \"clipboard\" &&\n action.selection !== \"primary\"\n ) {\n throw new Error(\"clipboard selection must be clipboard or primary\");\n }\n if (\n action.action === \"clipboard_write\" &&\n (typeof action.text !== \"string\" || Buffer.byteLength(action.text) > maxClipboardBytes)\n ) {\n throw new Error(`clipboard text must be at most ${maxClipboardBytes} bytes`);\n }\n}\n\nfunction validateTypingOptions(action, prefix) {\n if (\n \"typingDelayMs\" in action &&\n (!Number.isInteger(action.typingDelayMs) ||\n action.typingDelayMs < 0 ||\n action.typingDelayMs > 200)\n ) {\n throw new Error(`${prefix}typingDelayMs must be an integer from 0 to 200`);\n }\n if (\"typingStyle\" in action && action.typingStyle !== \"natural\") {\n throw new Error(`${prefix}typingStyle must be natural`);\n }\n if (\"typingStyle\" in action && \"typingDelayMs\" in action) {\n throw new Error(`${prefix}type action cannot set both typingStyle and typingDelayMs`);\n }\n}\n\nfunction validateArrangeWindowsAction(action) {\n if (!Number.isInteger(action.waitForMs) || action.waitForMs < 0 || action.waitForMs > 10_000) {\n throw new Error(\"arrange_windows waitForMs must be an integer from 0 to 10000\");\n }\n validateWindowScene(action.windows);\n}\n\nfunction isArtifactPath(value, extension) {\n if (typeof value !== \"string\" || !value.endsWith(extension)) return false;\n const path = resolve(value);\n return dirname(path) === workspaceRoot;\n}\n\nasync function readBody(request) {\n let body = \"\";\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > maxRequestBytes) {\n throw new Error(\"request exceeds 32 KiB\");\n }\n }\n return body;\n}\n\nfunction sendJson(response, statusCode, body) {\n if (response.destroyed) return;\n const json = JSON.stringify(body);\n response.writeHead(statusCode, {\n \"content-length\": Buffer.byteLength(json),\n \"content-type\": \"application/json\",\n });\n response.end(json);\n}\n"; export declare const CLIENT_MJS_SOURCE = "import http from \"node:http\";\nimport { env } from \"node:process\";\n\nconst socketPath = env.COMPUTER_USE_SOCKET_PATH;\nconst body = env.COMPUTER_USE_REQUEST;\n\nif (body === undefined) {\n throw new Error(\"COMPUTER_USE_REQUEST is required\");\n}\n\nconst deadline = Date.now() + 120_000;\nconst response = await new Promise((resolve, reject) => {\n const request = http.request(\n {\n socketPath,\n method: \"POST\",\n path: \"/call\",\n headers: {\n \"content-length\": Buffer.byteLength(body),\n \"content-type\": \"application/json\",\n \"x-computer-use-deadline\": String(deadline),\n },\n },\n (incoming) => {\n incoming.on(\"error\", reject);\n incoming.setEncoding(\"utf8\");\n let responseBody = \"\";\n incoming.on(\"data\", (chunk) => {\n responseBody += chunk;\n });\n incoming.on(\"end\", () => {\n resolve({ body: responseBody, statusCode: incoming.statusCode ?? 500 });\n });\n },\n );\n const timer = setTimeout(\n () => request.destroy(new Error(\"computer-use request deadline exceeded\")),\n Math.max(0, deadline - Date.now()),\n );\n request.once(\"close\", () => clearTimeout(timer));\n request.on(\"error\", reject);\n request.end(body);\n});\n\nif (response.statusCode !== 200) {\n process.stderr.write(response.body);\n process.exitCode = 1;\n} else {\n process.stdout.write(response.body);\n}\n"; export declare const TYPING_MJS_SOURCE = "const DEMO_TYPING_SPEED = 1.2;\nconst MIN_DELAY_MS = 35;\nconst MAX_DELAY_MS = 220;\n\nexport function naturalTypingPlan(text) {\n const graphemes = [...new Intl.Segmenter().segment(text)].map(({ segment }) => segment);\n let state = hashText(text) || 1;\n return graphemes.map((grapheme, index) => {\n state = nextState(state);\n return {\n grapheme,\n delayMs: index === graphemes.length - 1 ? 0 : naturalDelay(grapheme, state),\n };\n });\n}\n\nfunction naturalDelay(grapheme, state) {\n const jitter = state / 0x1_0000_0000;\n const range = delayRange(grapheme);\n return Math.round((range.min + (range.max - range.min) * jitter) / DEMO_TYPING_SPEED);\n}\n\nfunction delayRange(grapheme) {\n if (/\\s/u.test(grapheme)) return { min: 75, max: 125 };\n if (/[.!?]/u.test(grapheme)) return { min: 145, max: MAX_DELAY_MS };\n if (/[/|;&]/u.test(grapheme)) return { min: 105, max: 175 };\n if (/[-_=,:]/u.test(grapheme)) return { min: 70, max: 125 };\n return { min: MIN_DELAY_MS, max: 85 };\n}\n\nfunction hashText(text) {\n let hash = 2166136261;\n for (const character of text) {\n hash ^= character.codePointAt(0);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\nfunction nextState(state) {\n let value = state;\n value ^= value << 13;\n value ^= value >>> 17;\n value ^= value << 5;\n return value >>> 0;\n}\n"; export declare const POINTER_MOTION_MJS_SOURCE = "const FRAME_MS = 16;\n\nexport function pointerMotionPlan(start, end, options = {}) {\n const style = options.style ?? \"natural\";\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n const durationMs = options.durationMs ?? naturalDuration(distance, style);\n if (distance < 2 || durationMs === 0) {\n return { phases: [[{ ...end, delayMs: 0 }]], settleMs: 0 };\n }\n\n if (style === \"precision\" && distance >= 120) {\n const ratio = 0.92 + deterministicUnit(start, end, 7) * 0.04;\n const undershoot = {\n x: Math.round(start.x + (end.x - start.x) * ratio),\n y: Math.round(start.y + (end.y - start.y) * ratio),\n };\n // Cover most of the distance quickly, then spend a disproportionate\n // share of the duration continuously correcting the final 4\u20138%.\n const firstDuration = Math.round(durationMs * 0.62);\n return {\n phases: [\n bezierPhase(start, undershoot, firstDuration, \"natural\", 11),\n bezierPhase(undershoot, end, durationMs - firstDuration, \"precision\", 23),\n ],\n settleMs: 0,\n };\n }\n\n return {\n phases: [bezierPhase(start, end, durationMs, style, 17)],\n settleMs: style === \"natural\" ? 15 + Math.round(deterministicUnit(start, end, 31) * 15) : 0,\n };\n}\n\nfunction bezierPhase(start, end, durationMs, style, salt) {\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n const steps = Math.max(2, Math.min(60, Math.round(durationMs / FRAME_MS)));\n const perpendicular = unitPerpendicular(start, end);\n const bendLimit = style === \"precision\" ? 8 : Math.min(70, distance * 0.09);\n const bend = (deterministicUnit(start, end, salt) * 2 - 1) * bendLimit;\n const c1 = {\n x: start.x + (end.x - start.x) * 0.28 + perpendicular.x * bend,\n y: start.y + (end.y - start.y) * 0.28 + perpendicular.y * bend,\n };\n const c2 = {\n x: start.x + (end.x - start.x) * 0.76 + perpendicular.x * bend * 0.65,\n y: start.y + (end.y - start.y) * 0.76 + perpendicular.y * bend * 0.65,\n };\n const delayMs = durationMs / steps;\n return Array.from({ length: steps }, (_, index) => {\n // This asymmetric timing curve accelerates quickly and eases into the\n // target without the perfectly mirrored feel of smoothstep.\n const progress = (index + 1) / steps;\n const t = 1 - (1 - progress) ** 2.35;\n const point = cubicBezier(start, c1, c2, end, t);\n return {\n x: index === steps - 1 ? end.x : Math.round(point.x),\n y: index === steps - 1 ? end.y : Math.round(point.y),\n delayMs: index === steps - 1 ? 0 : delayMs,\n };\n });\n}\n\nfunction cubicBezier(start, c1, c2, end, t) {\n const inverse = 1 - t;\n return {\n x:\n inverse ** 3 * start.x +\n 3 * inverse ** 2 * t * c1.x +\n 3 * inverse * t ** 2 * c2.x +\n t ** 3 * end.x,\n y:\n inverse ** 3 * start.y +\n 3 * inverse ** 2 * t * c1.y +\n 3 * inverse * t ** 2 * c2.y +\n t ** 3 * end.y,\n };\n}\n\nfunction naturalDuration(distance, style) {\n const base = style === \"precision\" ? 150 : 110;\n const scale = style === \"precision\" ? 0.35 : 0.28;\n const minimum = style === \"precision\" ? 180 : 140;\n const maximum = style === \"precision\" ? 650 : 550;\n return Math.round(Math.min(maximum, Math.max(minimum, base + distance * scale)));\n}\n\nfunction unitPerpendicular(start, end) {\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const length = Math.hypot(dx, dy) || 1;\n return { x: -dy / length, y: dx / length };\n}\n\nfunction deterministicUnit(start, end, salt) {\n let value =\n (Math.imul(Math.round(start.x), 73_856_093) ^\n Math.imul(Math.round(start.y), 19_349_663) ^\n Math.imul(Math.round(end.x), 83_492_791) ^\n Math.imul(Math.round(end.y), 2_654_435_761) ^\n salt) >>>\n 0;\n value ^= value << 13;\n value ^= value >>> 17;\n value ^= value << 5;\n return (value >>> 0) / 0x1_0000_0000;\n}\n"; export declare const COMPUTER_USE_DRIVER_SOURCES: { readonly "server.mjs": "import { setTimeout as delay } from \"node:timers/promises\";\nimport { execFile, execFileSync } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { chmod, rename, rm } from \"node:fs/promises\";\nimport { promisify } from \"node:util\";\nimport http from \"node:http\";\nimport { env } from \"node:process\";\nimport { dirname, resolve } from \"node:path\";\n\nimport { CuaDriver } from \"@trycua/cua-driver\";\n\nimport { pointerMotionPlan } from \"./pointer-motion.mjs\";\nimport { selectGroundingElements } from \"./state.mjs\";\nimport { naturalTypingPlan } from \"./typing.mjs\";\n\nconst socketPath = env.COMPUTER_USE_SOCKET_PATH;\nconst workspaceRoot = env.COMPUTER_USE_ROOT;\nconst maxRequestBytes = 32 * 1024;\nconst maxRequestMs = 120_000;\nconst maxRecordingMs = 300_000;\nconst nativeCallTimeoutMs = 10_000;\n// Only the queue owner sets this signal; recording finalization deliberately bypasses it.\nlet requestSignal;\nconst desktopMargin = 24;\nconst minimumWindow = { height: 240, width: 480 };\nconst layoutTolerance = { height: 8, width: 8, x: 4, y: 4 };\nconst maxClipboardBytes = 10_000;\nconst driver = await CuaDriver.create({ claudeCodeCompatibility: false });\nconst execFilePromise = promisify(execFile);\nfunction execFileAsync(file, args, options) {\n return execFilePromise(file, args, {\n timeout: nativeCallTimeoutMs,\n ...options,\n signal: requestSignal,\n });\n}\n\nlet activeRecording = null;\nlet queue = Promise.resolve();\nlet shuttingDown = false;\n\nawait rm(socketPath, { force: true });\n\nconst server = http.createServer((request, response) => {\n if (shuttingDown) return response.destroy();\n if (request.method !== \"POST\" || request.url !== \"/call\") {\n sendJson(response, 404, { error: \"not found\" });\n return;\n }\n const controller = new AbortController();\n const deadline = Number(request.headers[\"x-computer-use-deadline\"]);\n const timeoutMs = Number.isFinite(deadline)\n ? Math.max(0, Math.min(maxRequestMs, deadline - Date.now()))\n : maxRequestMs;\n const cancel = () =>\n controller.abort(new Error(\"computer-use request cancelled or deadline exceeded\"));\n const timer = setTimeout(cancel, timeoutMs);\n response.once(\"close\", cancel);\n controller.signal.addEventListener(\"abort\", () => response.destroy(), { once: true });\n if (timeoutMs === 0) cancel();\n // Read immediately: a body may finish or disconnect while another call owns the queue.\n const body = readBody(request);\n void body.catch(() => undefined);\n const task = queue.then(async () => {\n controller.signal.throwIfAborted();\n if (shuttingDown) return;\n requestSignal = controller.signal;\n try {\n await handleRequest(await body, response);\n } finally {\n requestSignal = undefined;\n }\n });\n queue = task\n .catch(() => response.destroy())\n .finally(() => {\n clearTimeout(timer);\n response.removeListener(\"close\", cancel);\n });\n});\n\nserver.listen(socketPath, () => {\n void chmod(socketPath, 0o600);\n});\n\nfor (const signal of [\"SIGINT\", \"SIGTERM\"]) {\n process.once(signal, () => {\n void shutdown();\n });\n}\n\nasync function shutdown() {\n if (shuttingDown) return;\n shuttingDown = true;\n server.close();\n server.closeAllConnections();\n const timer = setTimeout(() => process.exit(1), nativeCallTimeoutMs * 2);\n let exitCode = 0;\n try {\n await queue;\n if (activeRecording !== null) await stopRecording();\n } catch {\n exitCode = 1;\n } finally {\n try {\n await driver.shutdown();\n } catch {\n exitCode = 1;\n } finally {\n clearTimeout(timer);\n process.exit(exitCode);\n }\n }\n}\n\nasync function handleRequest(body, response) {\n try {\n const input = JSON.parse(body);\n if (input.action === \"health\") {\n sendJson(response, 200, { backend: \"cua-driver\", version: \"0.12.5\" });\n return;\n }\n\n validateRequest(input);\n const actionResult = await performAction(input.action);\n const state = await captureState(input.screenshotPath);\n sendJson(response, 200, {\n action: actionResult,\n backend: \"cua-driver\",\n screenshot: input.screenshotPath,\n state,\n version: \"0.12.5\",\n });\n } catch (error) {\n if (requestSignal?.aborted && activeRecording !== null) {\n await stopRecording().catch(() => undefined);\n }\n sendJson(response, 500, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nasync function performAction(action) {\n requestSignal?.throwIfAborted();\n validateActionCoordinates(action);\n if (action.action === \"sequence\") {\n const results = [];\n try {\n for (const nested of action.actions) {\n const startedAt = performance.now();\n const result = await performAction(nested);\n results.push({\n action: nested.action,\n durationMs: Math.round(performance.now() - startedAt),\n result,\n });\n }\n return results;\n } catch (error) {\n // Never strand the durable driver in an active-recording state when a\n // rehearsed step fails. Preserve the original action error.\n if (activeRecording !== null) await stopRecording().catch(() => undefined);\n throw error;\n }\n }\n switch (action.action) {\n case \"screenshot\":\n return null;\n case \"click\":\n await moveCursor(action);\n return click(action, 1);\n case \"double_click\":\n await moveCursor(action);\n return click(action, 2);\n case \"triple_click\":\n await moveCursor(action);\n return click(action, 3);\n case \"drag\":\n return dragCursor(action);\n case \"keypress\":\n return action.keys.length === 1\n ? call(\"press_key\", {\n scope: \"desktop\",\n key: normalizeKey(action.keys[0]),\n })\n : call(\"hotkey\", {\n scope: \"desktop\",\n keys: action.keys.map(normalizeKey),\n });\n case \"move\":\n return moveCursor(action);\n case \"scroll\": {\n await call(\"move_cursor\", {\n scope: \"desktop\",\n x: action.x,\n y: action.y,\n });\n const results = [];\n if (action.scrollY !== 0) {\n results.push(await call(\"scroll\", scrollInput(action.scrollY, \"up\", \"down\")));\n }\n if (action.scrollX !== 0) {\n results.push(await call(\"scroll\", scrollInput(action.scrollX, \"left\", \"right\")));\n }\n return results;\n }\n case \"type\":\n return typeText(action);\n case \"clipboard_read\":\n return readClipboard(action.selection);\n case \"clipboard_write\":\n return writeClipboard(action.selection, action.text);\n case \"wait\":\n await sleep(action.durationMs ?? 2_000);\n return null;\n case \"arrange_windows\":\n return arrangeWindows(action);\n case \"record_start\":\n return startRecording(action.path);\n case \"record_stop\":\n return stopRecording();\n default:\n throw new Error(`unsupported computer-use action: ${String(action.action)}`);\n }\n}\n\nasync function captureState(screenshotPath) {\n await call(\"get_desktop_state\", { screenshot_out_file: screenshotPath });\n const pointer = structured(await call(\"get_cursor_position\", {}));\n const screen = x11ScreenSize();\n const windows = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n const contentWindows = Array.isArray(windows) ? windows.filter(isContentWindow) : [];\n const groundingWindow = contentWindows.toSorted((left, right) => right.z_index - left.z_index)[0];\n\n let elements = [];\n if (groundingWindow !== undefined) {\n const windowState = structured(\n await call(\"get_window_state\", {\n pid: groundingWindow.pid,\n window_id: groundingWindow.window_id,\n include_screenshot: false,\n max_depth: 20,\n max_elements: 250,\n }),\n );\n if (Array.isArray(windowState.elements)) {\n elements = selectGroundingElements(windowState.elements, {\n width: Number(screen.width) || 1920,\n height: Number(screen.height) || 1080,\n });\n }\n }\n\n return formatState(groundingWindow, pointer, elements);\n}\n\nasync function startRecording(path) {\n if (activeRecording !== null) {\n throw new Error(\"a recording is already active\");\n }\n\n const outputDirectory = `${workspaceRoot}/recording-${randomUUID()}`;\n const result = await call(\n \"start_recording\",\n {\n output_dir: outputDirectory,\n record_video: true,\n },\n false,\n );\n activeRecording = {\n outputDirectory,\n outputPath: path,\n stopping: null,\n timer: setTimeout(() => {\n void stopRecording().catch(() => {\n void shutdown();\n });\n }, maxRecordingMs),\n };\n return result;\n}\n\nasync function stopRecording() {\n if (activeRecording === null) {\n throw new Error(\"no recording is active\");\n }\n\n const recording = activeRecording;\n if (recording.stopping !== null) return recording.stopping;\n clearTimeout(recording.timer);\n recording.stopping = finalizeRecording(recording);\n return recording.stopping;\n}\n\nasync function finalizeRecording(recording) {\n try {\n const result = await call(\"stop_recording\", {}, false);\n const output = structured(result);\n const videoPath =\n typeof output.last_video_path === \"string\"\n ? output.last_video_path\n : recording.outputDirectory + \"/recording.mp4\";\n await rename(videoPath, recording.outputPath);\n await rm(recording.outputDirectory, { force: true, recursive: true });\n return result;\n } catch (error) {\n void shutdown();\n throw error;\n } finally {\n activeRecording = null;\n }\n}\n\nasync function call(name, input, cancellable = true) {\n const signal = cancellable ? requestSignal : undefined;\n signal?.throwIfAborted();\n let timer;\n const result = await Promise.race([\n driver.callTool(name, JSON.stringify(input)),\n new Promise((_, reject) => {\n timer = setTimeout(() => {\n // Native calls have no abort API. Do not reuse a driver with uncertain in-flight work.\n void shutdown();\n reject(new Error(\"computer-use native call timed out; restart with startComputerUse\"));\n }, nativeCallTimeoutMs);\n }),\n ]).finally(() => clearTimeout(timer));\n signal?.throwIfAborted();\n if (result.isError) {\n throw new Error(\n `Cua ${name} failed${result.errorCode ? ` (${result.errorCode})` : \"\"}: ${result.text}`,\n );\n }\n return {\n degraded: result.degraded,\n structuredJson: result.structuredJson,\n text: result.text,\n verified: result.verified,\n };\n}\n\nfunction structured(result) {\n if (result.structuredJson === undefined) return {};\n return JSON.parse(result.structuredJson);\n}\n\nfunction pointerClickInput(x, y, button, count) {\n return {\n scope: \"desktop\",\n x,\n y,\n button: button === \"wheel\" ? \"middle\" : button,\n count,\n };\n}\n\nasync function moveCursor(action) {\n if (action.movementStyle === undefined) {\n return call(\"move_cursor\", { scope: \"desktop\", x: action.x, y: action.y });\n }\n const current = structured(await call(\"get_cursor_position\", {}));\n const start = { x: Number(current.x), y: Number(current.y) };\n if (!Number.isFinite(start.x) || !Number.isFinite(start.y)) {\n return call(\"move_cursor\", { scope: \"desktop\", x: action.x, y: action.y });\n }\n const options = { style: action.movementStyle };\n if (action.durationMs !== undefined) options.durationMs = action.durationMs;\n return followPointerPlan(pointerMotionPlan(start, { x: action.x, y: action.y }, options), \"x11\");\n}\n\nasync function dragCursor(action) {\n const start = action.path[0];\n const end = action.path.at(-1);\n if (action.movementStyle === undefined) {\n return call(\"drag\", {\n scope: \"desktop\",\n from_x: start.x,\n from_y: start.y,\n to_x: end.x,\n to_y: end.y,\n steps: Math.min(200, Math.max(20, action.path.length * 10)),\n });\n }\n const style = action.movementStyle;\n const current = structured(await call(\"get_cursor_position\", {}));\n const currentPoint = { x: Number(current.x), y: Number(current.y) };\n const approach =\n Number.isFinite(currentPoint.x) && Number.isFinite(currentPoint.y)\n ? pointerPlanArgs(pointerMotionPlan(currentPoint, start, { style: \"natural\" }))\n : [\"mousemove\", String(start.x), String(start.y)];\n const options = { style };\n if (action.durationMs !== undefined) options.durationMs = action.durationMs;\n const drag = pointerPlanArgs(pointerMotionPlan(start, end, options));\n return xdotool([\n ...approach,\n \"sleep\",\n \"0.025\",\n \"mousedown\",\n \"1\",\n \"sleep\",\n \"0.030\",\n ...drag,\n \"sleep\",\n \"0.030\",\n \"mouseup\",\n \"1\",\n ]);\n}\n\nasync function followPointerPlan(plan, backend = \"cua\") {\n if (backend === \"x11\") return xdotool(pointerPlanArgs(plan));\n const results = [];\n for (let phaseIndex = 0; phaseIndex < plan.phases.length; phaseIndex += 1) {\n for (const point of plan.phases[phaseIndex]) {\n results.push(await call(\"move_cursor\", { scope: \"desktop\", x: point.x, y: point.y }));\n if (point.delayMs > 0) await sleep(point.delayMs);\n }\n if (phaseIndex < plan.phases.length - 1 && plan.settleMs > 0) await sleep(plan.settleMs);\n }\n if (plan.phases.length === 1 && plan.settleMs > 0) await sleep(plan.settleMs);\n return results;\n}\n\nfunction pointerPlanArgs(plan) {\n const args = [];\n for (let phaseIndex = 0; phaseIndex < plan.phases.length; phaseIndex += 1) {\n for (const point of plan.phases[phaseIndex]) {\n args.push(\"mousemove\", String(point.x), String(point.y));\n if (point.delayMs > 0) args.push(\"sleep\", (point.delayMs / 1000).toFixed(3));\n }\n if (phaseIndex < plan.phases.length - 1 && plan.settleMs > 0) {\n args.push(\"sleep\", (plan.settleMs / 1000).toFixed(3));\n }\n }\n if (plan.phases.length === 1 && plan.settleMs > 0) {\n args.push(\"sleep\", (plan.settleMs / 1000).toFixed(3));\n }\n return args;\n}\n\nasync function xdotool(args) {\n const plannedSleepMs = args.reduce(\n (total, value, index) => (args[index - 1] === \"sleep\" ? total + Number(value) * 1000 : total),\n 0,\n );\n const timeout = Math.max(2_000, Math.ceil(plannedSleepMs + 1_500));\n const result = await execFileAsync(\"xdotool\", args, { env: displayEnv(), timeout });\n return {\n plannedSleepMs: Math.round(plannedSleepMs),\n stderr: result.stderr,\n stdout: result.stdout,\n };\n}\n\nfunction typingPlan(action) {\n if (action.typingStyle === \"natural\") return naturalTypingPlan(action.text);\n return [...new Intl.Segmenter().segment(action.text)].map(({ segment }) => ({\n grapheme: segment,\n delayMs: action.typingDelayMs ?? 0,\n }));\n}\n\nfunction validateTypingBudget(actions) {\n let characters = 0;\n let calls = 0;\n let delayMs = 0;\n for (const action of actions) {\n if (action.action !== \"type\") continue;\n if (typeof action.text !== \"string\" || action.text.length > 10_000) {\n throw new Error(\"typing text must be at most 10000 characters\");\n }\n characters += action.text.length;\n if (action.typingStyle === undefined && !action.typingDelayMs) continue;\n const plan = typingPlan(action);\n calls += plan.length;\n delayMs += plan.reduce((total, step) => total + step.delayMs, 0);\n }\n if (characters > 10_000 || calls > 1_000 || delayMs > 30_000) {\n throw new Error(\n \"typing budget exceeded: at most 10000 characters, 1000 paced graphemes and 30000ms delay per request; use unpaced typing for longer text\",\n );\n }\n}\n\nasync function typeText(action) {\n if (action.typingStyle === undefined && !action.typingDelayMs) {\n return call(\"type_text\", { scope: \"desktop\", text: action.text });\n }\n let result;\n let graphemesTyped = 0;\n for (const { grapheme, delayMs } of typingPlan(action)) {\n result = await call(\"type_text\", { scope: \"desktop\", text: grapheme });\n graphemesTyped += 1;\n if (delayMs > 0) await sleep(delayMs);\n }\n return { graphemesTyped, result };\n}\n\nasync function click(action, count) {\n if (action.button !== \"back\" && action.button !== \"forward\") {\n return call(\"click\", pointerClickInput(action.x, action.y, action.button, count));\n }\n\n await call(\"move_cursor\", {\n scope: \"desktop\",\n x: action.x,\n y: action.y,\n });\n const results = [];\n for (let index = 0; index < count; index += 1) {\n results.push(\n await call(\"hotkey\", {\n scope: \"desktop\",\n keys: [\"alt\", action.button === \"back\" ? \"left\" : \"right\"],\n }),\n );\n }\n return results;\n}\n\nasync function readClipboard(selection) {\n try {\n const result = await execFileAsync(\"xclip\", [\"-selection\", xclipSelection(selection), \"-out\"], {\n env: displayEnv(),\n maxBuffer: maxClipboardBytes + 1,\n });\n if (Buffer.byteLength(result.stdout) > maxClipboardBytes) {\n throw new Error(`clipboard content exceeds ${maxClipboardBytes} bytes`);\n }\n return { selection, text: result.stdout };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"clipboard content exceeds\")) throw error;\n return { selection, text: \"\" };\n }\n}\n\nasync function writeClipboard(selection, text) {\n if (Buffer.byteLength(text) > maxClipboardBytes) {\n throw new Error(`clipboard content exceeds ${maxClipboardBytes} bytes`);\n }\n await new Promise((resolve, reject) => {\n const child = execFile(\"xclip\", [\"-selection\", xclipSelection(selection), \"-in\"], {\n env: displayEnv(),\n timeout: nativeCallTimeoutMs,\n signal: requestSignal,\n });\n child.once(\"error\", reject);\n child.once(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`xclip exited ${code}`)),\n );\n child.stdin.end(text);\n });\n return { bytes: Buffer.byteLength(text), selection };\n}\n\nfunction xclipSelection(selection) {\n return selection === \"primary\" ? \"primary\" : \"clipboard\";\n}\n\nasync function arrangeWindows(action) {\n const scene = validateWindowScene(action.windows);\n const deadline = Date.now() + action.waitForMs;\n let resolved = null;\n while (resolved === null) {\n const windows = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n resolved = resolveSceneWindows(scene, Array.isArray(windows) ? windows : []);\n if (resolved !== null) break;\n if (Date.now() >= deadline) {\n throw new Error(\n `arrange_windows timed out waiting for: ${missingSceneSelectors(scene, Array.isArray(windows) ? windows : []).join(\", \")}`,\n );\n }\n await sleep(100);\n }\n\n // Resolve the complete scene before mutating any window. A missing selector\n // therefore leaves the existing desktop untouched.\n for (const item of resolved) {\n const windowId = String(item.window.window_id);\n await execFileAsync(\"xdotool\", [\"windowactivate\", \"--sync\", windowId], { env: displayEnv() });\n await execFileAsync(\n \"xdotool\",\n [\"windowmove\", windowId, String(item.bounds.x), String(item.bounds.y)],\n {\n env: displayEnv(),\n },\n );\n await execFileAsync(\n \"xdotool\",\n [\"windowsize\", windowId, String(item.bounds.width), String(item.bounds.height)],\n { env: displayEnv() },\n );\n }\n\n let verified = null;\n let windows = [];\n for (let attempt = 0; attempt < 10; attempt += 1) {\n const response = structured(await call(\"list_windows\", { on_screen_only: true })).windows;\n windows = Array.isArray(response) ? response : [];\n verified = resolveSceneWindows(scene, windows);\n if (\n verified !== null &&\n verified.every(({ bounds, window }) => approximatelyEqual(bounds, window))\n ) {\n return verified.map(layoutResult);\n }\n await sleep(100);\n }\n\n const screenshotPath = `${workspaceRoot}/layout-failure-${Date.now()}.png`;\n await call(\"get_desktop_state\", { screenshot_out_file: screenshotPath });\n const details =\n verified === null\n ? { stage: \"selector_resolution\", missing: missingSceneSelectors(scene, windows) }\n : {\n stage: \"verification\",\n tolerance: layoutTolerance,\n windows: verified.map(layoutResult),\n };\n throw new Error(\n `arrange_windows failed: ${JSON.stringify({ ...details, screenshot: screenshotPath })}`,\n );\n}\n\nfunction validateWindowScene(windows) {\n if (!Array.isArray(windows) || windows.length < 1 || windows.length > 4) {\n throw new Error(\"arrange_windows requires 1 to 4 windows\");\n }\n const scene = windows.map((item) => ({ ...item, bounds: regionBounds(item.region) }));\n const selectors = scene.map((item) => selectorLabel(item.selector));\n if (new Set(selectors).size !== selectors.length) {\n throw new Error(\"arrange_windows selectors must be unique\");\n }\n for (let left = 0; left < scene.length; left += 1) {\n for (let right = left + 1; right < scene.length; right += 1) {\n if (overlaps(scene[left].bounds, scene[right].bounds)) {\n throw new Error(\"arrange_windows regions must not overlap\");\n }\n }\n }\n return scene;\n}\n\nfunction desktopBounds() {\n const screen = x11ScreenSize();\n return {\n x: desktopMargin,\n y: desktopMargin,\n width: screen.width - desktopMargin * 2,\n height: screen.height - desktopMargin * 2,\n };\n}\n\nfunction x11ScreenSize() {\n // The X server is the source of truth after set_desktop_size. Avoid CUA's\n // session-oriented get_screen_size contract, which differs from pointer\n // actions and does not accept scope.\n const output = execFileSync(\"xdpyinfo\", [\"-display\", \":99\"], {\n encoding: \"utf8\",\n timeout: 2_000,\n });\n const match = /dimensions:\\s+(\\d+)x(\\d+) pixels/u.exec(output);\n if (match === null) throw new Error(\"could not determine desktop dimensions\");\n return { width: Number(match[1]), height: Number(match[2]) };\n}\n\nfunction regionBounds(region) {\n if (\n typeof region !== \"object\" ||\n region === null ||\n ![region.x, region.y, region.width, region.height].every(Number.isFinite) ||\n region.x < 0 ||\n region.y < 0 ||\n region.width <= 0 ||\n region.height <= 0 ||\n region.x + region.width > 1 ||\n region.y + region.height > 1\n ) {\n throw new Error(\"arrange_windows regions must fit within the desktop\");\n }\n const desktop = desktopBounds();\n const left = Math.round(desktop.x + desktop.width * region.x);\n const top = Math.round(desktop.y + desktop.height * region.y);\n const right = Math.round(desktop.x + desktop.width * (region.x + region.width));\n const bottom = Math.round(desktop.y + desktop.height * (region.y + region.height));\n const bounds = { x: left, y: top, width: right - left, height: bottom - top };\n if (bounds.width < minimumWindow.width || bounds.height < minimumWindow.height) {\n throw new Error(\n `arrange_windows regions must be at least ${minimumWindow.width}x${minimumWindow.height}`,\n );\n }\n return bounds;\n}\n\nfunction resolveSceneWindows(scene, windows) {\n const resolved = scene.map((item) => {\n const window = windows.find((candidate) => matchesSelector(candidate, item.selector));\n return window === undefined ? null : { bounds: item.bounds, window };\n });\n return resolved.includes(null) ? null : resolved;\n}\n\nfunction missingSceneSelectors(scene, windows) {\n return scene\n .filter((item) => !windows.some((window) => matchesSelector(window, item.selector)))\n .map((item) => selectorLabel(item.selector));\n}\n\nfunction matchesSelector(window, selector) {\n const identity = `${window.app_name ?? \"\"}\\n${window.title ?? \"\"}`.toLowerCase();\n if (selector.app === \"firefox\") return identity.includes(\"firefox\");\n const title = selector.instance === \"primary\" ? \"terminal 1\" : \"terminal 2\";\n return identity.includes(title);\n}\n\nfunction selectorLabel(selector) {\n return selector.app === \"firefox\" ? \"firefox\" : `xterm:${selector.instance}`;\n}\n\nfunction overlaps(left, right) {\n return (\n left.x < right.x + right.width &&\n left.x + left.width > right.x &&\n left.y < right.y + right.height &&\n left.y + left.height > right.y\n );\n}\n\nfunction layoutResult({ bounds, window }) {\n const observed = {\n x: Number(window.x),\n y: Number(window.y),\n width: Number(window.width),\n height: Number(window.height),\n };\n return {\n selector: `${window.app_name ?? \"\"}:${window.title ?? \"\"}`,\n requested: bounds,\n observed,\n delta: {\n x: observed.x - bounds.x,\n y: observed.y - bounds.y,\n width: observed.width - bounds.width,\n height: observed.height - bounds.height,\n },\n tolerance: layoutTolerance,\n title: window.title,\n window_id: window.window_id,\n };\n}\n\nfunction displayEnv() {\n return { ...env, DISPLAY: \":99\" };\n}\n\nfunction approximatelyEqual(bounds, window) {\n return (\n Math.abs(bounds.x - window.x) <= layoutTolerance.x &&\n Math.abs(bounds.y - window.y) <= layoutTolerance.y &&\n Math.abs(bounds.width - window.width) <= layoutTolerance.width &&\n Math.abs(bounds.height - window.height) <= layoutTolerance.height\n );\n}\n\nfunction sleep(milliseconds) {\n return delay(milliseconds, undefined, { signal: requestSignal });\n}\n\nfunction scrollInput(delta, negativeDirection, positiveDirection) {\n return {\n scope: \"desktop\",\n direction: delta < 0 ? negativeDirection : positiveDirection,\n amount: Math.min(50, Math.max(1, Math.round(Math.abs(delta) / 100))),\n by: \"line\",\n };\n}\n\nfunction normalizeKey(key) {\n const aliases = {\n ARROWDOWN: \"down\",\n ARROWLEFT: \"left\",\n ARROWRIGHT: \"right\",\n ARROWUP: \"up\",\n CMD: \"meta\",\n COMMAND: \"meta\",\n CONTROL: \"ctrl\",\n DEL: \"delete\",\n ENTER: \"enter\",\n ESC: \"escape\",\n OPTION: \"alt\",\n RETURN: \"enter\",\n };\n return aliases[key] ?? key.toLowerCase();\n}\n\nfunction isContentWindow(window) {\n const identity = `${window.app_name ?? \"\"}\\n${window.title ?? \"\"}`.toLowerCase();\n return (\n window.width >= 200 &&\n window.height >= 100 &&\n !identity.includes(\"xfce4-panel\") &&\n !identity.includes(\"xfdesktop\") &&\n window.title !== \"Desktop\"\n );\n}\n\nfunction formatState(groundingWindow, pointer, elements) {\n const lines = [\n groundingWindow === undefined\n ? \"grounding_window\\t(none)\"\n : `grounding_window\\t${groundingWindow.title} pid=${groundingWindow.pid} window_id=${groundingWindow.window_id}`,\n `pointer\\tx=${pointer.x ?? \"unknown\"} y=${pointer.y ?? \"unknown\"}`,\n \"accessibility_elements\",\n ];\n\n for (const element of elements) {\n const frame =\n element.frame === undefined\n ? \"\"\n : ` x=${element.frame.x} y=${element.frame.y} width=${element.frame.w} height=${element.frame.h}`;\n lines.push(\n `[${element.element_index}] role=${element.role}${frame} label=${JSON.stringify(element.label ?? \"\")}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\nfunction validateActionCoordinates(action) {\n const screen = x11ScreenSize();\n const points = [];\n if ([\"click\", \"double_click\", \"triple_click\", \"move\", \"scroll\"].includes(action.action)) {\n points.push({ x: action.x, y: action.y });\n }\n if (action.action === \"drag\") points.push(...action.path);\n for (const point of points) {\n if (\n !Number.isFinite(point.x) ||\n !Number.isFinite(point.y) ||\n point.x < 0 ||\n point.y < 0 ||\n point.x >= screen.width ||\n point.y >= screen.height\n ) {\n throw new Error(\n `computer-use coordinate is outside the ${screen.width}x${screen.height} desktop`,\n );\n }\n }\n}\n\nfunction validateRequest(input) {\n if (typeof input !== \"object\" || input === null) {\n throw new Error(\"request must be an object\");\n }\n if (!isArtifactPath(input.screenshotPath, \".png\")) {\n throw new Error(\"screenshot path must be a PNG under the computer-use workspace\");\n }\n if (typeof input.action !== \"object\" || input.action === null) {\n throw new Error(\"request action must be an object\");\n }\n const actions = new Set([\n \"arrange_windows\",\n \"click\",\n \"clipboard_read\",\n \"clipboard_write\",\n \"double_click\",\n \"drag\",\n \"keypress\",\n \"move\",\n \"record_start\",\n \"record_stop\",\n \"screenshot\",\n \"scroll\",\n \"sequence\",\n \"triple_click\",\n \"type\",\n \"wait\",\n ]);\n if (!actions.has(input.action.action)) {\n throw new Error(`unsupported computer-use action: ${String(input.action.action)}`);\n }\n if (input.action.action === \"sequence\") {\n if (\n !Array.isArray(input.action.actions) ||\n input.action.actions.length < 1 ||\n input.action.actions.length > 30\n ) {\n throw new Error(\"sequence must contain 1 to 30 actions\");\n }\n for (const action of input.action.actions) validateNestedSequenceAction(action);\n }\n if (input.action.action === \"arrange_windows\") validateArrangeWindowsAction(input.action);\n if (input.action.action === \"record_start\" && !isArtifactPath(input.action.path, \".mp4\")) {\n throw new Error(\"recording path must be an MP4 under the computer-use workspace\");\n }\n if (\n [\"click\", \"double_click\", \"triple_click\", \"drag\", \"move\"].includes(input.action.action) &&\n \"durationMs\" in input.action &&\n (!Number.isInteger(input.action.durationMs) ||\n input.action.durationMs < 0 ||\n input.action.durationMs > 2000)\n ) {\n throw new Error(\"pointer durationMs must be an integer from 0 to 2000\");\n }\n validateMovementStyle(input.action, \"\");\n if (\n input.action.action === \"wait\" &&\n (!Number.isInteger(input.action.durationMs) ||\n input.action.durationMs < 100 ||\n input.action.durationMs > 10_000)\n ) {\n throw new Error(\"wait durationMs must be an integer from 100 to 10000\");\n }\n validateClipboardOptions(input.action);\n validateTypingOptions(input.action, \"\");\n validateTypingBudget(input.action.action === \"sequence\" ? input.action.actions : [input.action]);\n}\n\nfunction validateNestedSequenceAction(action) {\n if (typeof action !== \"object\" || action === null)\n throw new Error(\"sequence action must be an object\");\n const allowed = new Set([\n \"arrange_windows\",\n \"click\",\n \"double_click\",\n \"drag\",\n \"keypress\",\n \"move\",\n \"record_start\",\n \"record_stop\",\n \"triple_click\",\n \"type\",\n \"wait\",\n ]);\n if (!allowed.has(action.action))\n throw new Error(`unsupported sequence action: ${String(action.action)}`);\n if (action.action === \"arrange_windows\") validateArrangeWindowsAction(action);\n if (action.action === \"record_start\" && !isArtifactPath(action.path, \".mp4\")) {\n throw new Error(\"sequence recording path must be an MP4 under the computer-use workspace\");\n }\n if (\n [\"click\", \"double_click\", \"triple_click\", \"drag\", \"move\"].includes(action.action) &&\n \"durationMs\" in action &&\n (!Number.isInteger(action.durationMs) || action.durationMs < 0 || action.durationMs > 2000)\n ) {\n throw new Error(\"sequence pointer durationMs must be an integer from 0 to 2000\");\n }\n validateMovementStyle(action, \"sequence \");\n if (\n action.action === \"wait\" &&\n (!Number.isInteger(action.durationMs) || action.durationMs < 100 || action.durationMs > 10_000)\n ) {\n throw new Error(\"sequence wait durationMs must be an integer from 100 to 10000\");\n }\n if (action.action === \"type\") validateTypingOptions(action, \"sequence \");\n}\n\nfunction validateMovementStyle(action, prefix) {\n if (\n \"movementStyle\" in action &&\n action.movementStyle !== \"natural\" &&\n action.movementStyle !== \"precision\"\n ) {\n throw new Error(`${prefix}movementStyle must be natural or precision`);\n }\n if (\n action.action === \"drag\" &&\n \"movementStyle\" in action &&\n action.movementStyle !== \"precision\"\n ) {\n throw new Error(`${prefix}drag movementStyle must be precision`);\n }\n}\n\nfunction validateClipboardOptions(action) {\n if (\n [\"clipboard_read\", \"clipboard_write\"].includes(action.action) &&\n action.selection !== \"clipboard\" &&\n action.selection !== \"primary\"\n ) {\n throw new Error(\"clipboard selection must be clipboard or primary\");\n }\n if (\n action.action === \"clipboard_write\" &&\n (typeof action.text !== \"string\" || Buffer.byteLength(action.text) > maxClipboardBytes)\n ) {\n throw new Error(`clipboard text must be at most ${maxClipboardBytes} bytes`);\n }\n}\n\nfunction validateTypingOptions(action, prefix) {\n if (\n \"typingDelayMs\" in action &&\n (!Number.isInteger(action.typingDelayMs) ||\n action.typingDelayMs < 0 ||\n action.typingDelayMs > 200)\n ) {\n throw new Error(`${prefix}typingDelayMs must be an integer from 0 to 200`);\n }\n if (\"typingStyle\" in action && action.typingStyle !== \"natural\") {\n throw new Error(`${prefix}typingStyle must be natural`);\n }\n if (\"typingStyle\" in action && \"typingDelayMs\" in action) {\n throw new Error(`${prefix}type action cannot set both typingStyle and typingDelayMs`);\n }\n}\n\nfunction validateArrangeWindowsAction(action) {\n if (!Number.isInteger(action.waitForMs) || action.waitForMs < 0 || action.waitForMs > 10_000) {\n throw new Error(\"arrange_windows waitForMs must be an integer from 0 to 10000\");\n }\n validateWindowScene(action.windows);\n}\n\nfunction isArtifactPath(value, extension) {\n if (typeof value !== \"string\" || !value.endsWith(extension)) return false;\n const path = resolve(value);\n return dirname(path) === workspaceRoot;\n}\n\nasync function readBody(request) {\n let body = \"\";\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > maxRequestBytes) {\n throw new Error(\"request exceeds 32 KiB\");\n }\n }\n return body;\n}\n\nfunction sendJson(response, statusCode, body) {\n if (response.destroyed) return;\n const json = JSON.stringify(body);\n response.writeHead(statusCode, {\n \"content-length\": Buffer.byteLength(json),\n \"content-type\": \"application/json\",\n });\n response.end(json);\n}\n"; readonly "client.mjs": "import http from \"node:http\";\nimport { env } from \"node:process\";\n\nconst socketPath = env.COMPUTER_USE_SOCKET_PATH;\nconst body = env.COMPUTER_USE_REQUEST;\n\nif (body === undefined) {\n throw new Error(\"COMPUTER_USE_REQUEST is required\");\n}\n\nconst deadline = Date.now() + 120_000;\nconst response = await new Promise((resolve, reject) => {\n const request = http.request(\n {\n socketPath,\n method: \"POST\",\n path: \"/call\",\n headers: {\n \"content-length\": Buffer.byteLength(body),\n \"content-type\": \"application/json\",\n \"x-computer-use-deadline\": String(deadline),\n },\n },\n (incoming) => {\n incoming.on(\"error\", reject);\n incoming.setEncoding(\"utf8\");\n let responseBody = \"\";\n incoming.on(\"data\", (chunk) => {\n responseBody += chunk;\n });\n incoming.on(\"end\", () => {\n resolve({ body: responseBody, statusCode: incoming.statusCode ?? 500 });\n });\n },\n );\n const timer = setTimeout(\n () => request.destroy(new Error(\"computer-use request deadline exceeded\")),\n Math.max(0, deadline - Date.now()),\n );\n request.once(\"close\", () => clearTimeout(timer));\n request.on(\"error\", reject);\n request.end(body);\n});\n\nif (response.statusCode !== 200) {\n process.stderr.write(response.body);\n process.exitCode = 1;\n} else {\n process.stdout.write(response.body);\n}\n"; readonly "state.mjs": "const MAX_GROUNDING_ELEMENTS = 80;\n\n/**\n * @typedef {{\n * element_index?: number;\n * frame?: { h?: number; w?: number; x?: number; y?: number };\n * label?: unknown;\n * [key: string]: unknown;\n * }} GroundingElement\n */\n\n/**\n * Keep the model-facing state aligned with the visible desktop screenshot.\n *\n * @param {unknown[]} elements\n * @returns {GroundingElement[]}\n */\nexport function selectGroundingElements(elements, screen = { width: 1920, height: 1080 }) {\n return elements\n .filter((element) => isVisibleLabeledElement(element, screen))\n .slice(0, MAX_GROUNDING_ELEMENTS);\n}\n\n/**\n * @param {unknown} value\n * @returns {value is GroundingElement}\n */\nfunction isVisibleLabeledElement(value, screen) {\n if (typeof value !== \"object\" || value === null) return false;\n const element = /** @type {GroundingElement} */ (value);\n const frame = element.frame;\n if (typeof element.label !== \"string\" || element.label.trim().length === 0) return false;\n if (typeof frame !== \"object\" || frame === null) return false;\n if (\n !Number.isFinite(frame.x) ||\n !Number.isFinite(frame.y) ||\n !Number.isFinite(frame.w) ||\n !Number.isFinite(frame.h)\n ) {\n return false;\n }\n\n const x = /** @type {number} */ (frame.x);\n const y = /** @type {number} */ (frame.y);\n const width = /** @type {number} */ (frame.w);\n const height = /** @type {number} */ (frame.h);\n return (\n width > 0 &&\n height > 0 &&\n x < screen.width &&\n y < screen.height &&\n x + width > 0 &&\n y + height > 0\n );\n}\n"; readonly "typing.mjs": "const DEMO_TYPING_SPEED = 1.2;\nconst MIN_DELAY_MS = 35;\nconst MAX_DELAY_MS = 220;\n\nexport function naturalTypingPlan(text) {\n const graphemes = [...new Intl.Segmenter().segment(text)].map(({ segment }) => segment);\n let state = hashText(text) || 1;\n return graphemes.map((grapheme, index) => {\n state = nextState(state);\n return {\n grapheme,\n delayMs: index === graphemes.length - 1 ? 0 : naturalDelay(grapheme, state),\n };\n });\n}\n\nfunction naturalDelay(grapheme, state) {\n const jitter = state / 0x1_0000_0000;\n const range = delayRange(grapheme);\n return Math.round((range.min + (range.max - range.min) * jitter) / DEMO_TYPING_SPEED);\n}\n\nfunction delayRange(grapheme) {\n if (/\\s/u.test(grapheme)) return { min: 75, max: 125 };\n if (/[.!?]/u.test(grapheme)) return { min: 145, max: MAX_DELAY_MS };\n if (/[/|;&]/u.test(grapheme)) return { min: 105, max: 175 };\n if (/[-_=,:]/u.test(grapheme)) return { min: 70, max: 125 };\n return { min: MIN_DELAY_MS, max: 85 };\n}\n\nfunction hashText(text) {\n let hash = 2166136261;\n for (const character of text) {\n hash ^= character.codePointAt(0);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\nfunction nextState(state) {\n let value = state;\n value ^= value << 13;\n value ^= value >>> 17;\n value ^= value << 5;\n return value >>> 0;\n}\n"; readonly "pointer-motion.mjs": "const FRAME_MS = 16;\n\nexport function pointerMotionPlan(start, end, options = {}) {\n const style = options.style ?? \"natural\";\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n const durationMs = options.durationMs ?? naturalDuration(distance, style);\n if (distance < 2 || durationMs === 0) {\n return { phases: [[{ ...end, delayMs: 0 }]], settleMs: 0 };\n }\n\n if (style === \"precision\" && distance >= 120) {\n const ratio = 0.92 + deterministicUnit(start, end, 7) * 0.04;\n const undershoot = {\n x: Math.round(start.x + (end.x - start.x) * ratio),\n y: Math.round(start.y + (end.y - start.y) * ratio),\n };\n // Cover most of the distance quickly, then spend a disproportionate\n // share of the duration continuously correcting the final 4–8%.\n const firstDuration = Math.round(durationMs * 0.62);\n return {\n phases: [\n bezierPhase(start, undershoot, firstDuration, \"natural\", 11),\n bezierPhase(undershoot, end, durationMs - firstDuration, \"precision\", 23),\n ],\n settleMs: 0,\n };\n }\n\n return {\n phases: [bezierPhase(start, end, durationMs, style, 17)],\n settleMs: style === \"natural\" ? 15 + Math.round(deterministicUnit(start, end, 31) * 15) : 0,\n };\n}\n\nfunction bezierPhase(start, end, durationMs, style, salt) {\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n const steps = Math.max(2, Math.min(60, Math.round(durationMs / FRAME_MS)));\n const perpendicular = unitPerpendicular(start, end);\n const bendLimit = style === \"precision\" ? 8 : Math.min(70, distance * 0.09);\n const bend = (deterministicUnit(start, end, salt) * 2 - 1) * bendLimit;\n const c1 = {\n x: start.x + (end.x - start.x) * 0.28 + perpendicular.x * bend,\n y: start.y + (end.y - start.y) * 0.28 + perpendicular.y * bend,\n };\n const c2 = {\n x: start.x + (end.x - start.x) * 0.76 + perpendicular.x * bend * 0.65,\n y: start.y + (end.y - start.y) * 0.76 + perpendicular.y * bend * 0.65,\n };\n const delayMs = durationMs / steps;\n return Array.from({ length: steps }, (_, index) => {\n // This asymmetric timing curve accelerates quickly and eases into the\n // target without the perfectly mirrored feel of smoothstep.\n const progress = (index + 1) / steps;\n const t = 1 - (1 - progress) ** 2.35;\n const point = cubicBezier(start, c1, c2, end, t);\n return {\n x: index === steps - 1 ? end.x : Math.round(point.x),\n y: index === steps - 1 ? end.y : Math.round(point.y),\n delayMs: index === steps - 1 ? 0 : delayMs,\n };\n });\n}\n\nfunction cubicBezier(start, c1, c2, end, t) {\n const inverse = 1 - t;\n return {\n x:\n inverse ** 3 * start.x +\n 3 * inverse ** 2 * t * c1.x +\n 3 * inverse * t ** 2 * c2.x +\n t ** 3 * end.x,\n y:\n inverse ** 3 * start.y +\n 3 * inverse ** 2 * t * c1.y +\n 3 * inverse * t ** 2 * c2.y +\n t ** 3 * end.y,\n };\n}\n\nfunction naturalDuration(distance, style) {\n const base = style === \"precision\" ? 150 : 110;\n const scale = style === \"precision\" ? 0.35 : 0.28;\n const minimum = style === \"precision\" ? 180 : 140;\n const maximum = style === \"precision\" ? 650 : 550;\n return Math.round(Math.min(maximum, Math.max(minimum, base + distance * scale)));\n}\n\nfunction unitPerpendicular(start, end) {\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const length = Math.hypot(dx, dy) || 1;\n return { x: -dy / length, y: dx / length };\n}\n\nfunction deterministicUnit(start, end, salt) {\n let value =\n (Math.imul(Math.round(start.x), 73_856_093) ^\n Math.imul(Math.round(start.y), 19_349_663) ^\n Math.imul(Math.round(end.x), 83_492_791) ^\n Math.imul(Math.round(end.y), 2_654_435_761) ^\n salt) >>>\n 0;\n value ^= value << 13;\n value ^= value >>> 17;\n value ^= value << 5;\n return (value >>> 0) / 0x1_0000_0000;\n}\n"; readonly "package.json": "{\n \"name\": \"computer-use-driver\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@trycua/cua-driver\": \"0.12.5\"\n }\n}\n"; };