Download src/gateway/desktop/computer-process.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 9.07 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/desktop/computer-process.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/gateway/desktop/computer-process.ts
-
curl -L -o computer-process.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/desktop/computer-process.ts
9.07 kB
| import { randomUUID } from "node:crypto"; | |
| import { resolveExecutablePath } from "../../infra/executable-path.js"; | |
| import { runtimeProcessEntrypoints } from "../../infra/runtime-process-entrypoints.js"; | |
| import { | |
| resolveRuntimeWorkerArgv, | |
| resolveRuntimeWorkerUrl, | |
| } from "../../infra/runtime-worker-url.js"; | |
| import { | |
| parseComputerUseCapabilityDescriptor, | |
| type ComputerUseCapabilityDescriptor, | |
| } from "../../plugins/computer-use-contract.js"; | |
| import { getProcessSupervisor } from "../../process/supervisor/index.js"; | |
| import type { ManagedRun, ProcessSupervisor, RunExit } from "../../process/supervisor/types.js"; | |
| import { createDeferredCore } from "../../shared/deferred.js"; | |
| import { createPendingRequestRegistry } from "../../shared/pending-request-registry.js"; | |
| import { | |
| ComputerHostFinalizationError, | |
| parseComputerHostOutput, | |
| type ComputerHostCommand, | |
| type ComputerHostExecutionClose, | |
| type ComputerHostInput, | |
| } from "./computer-protocol.js"; | |
| const STARTUP_TIMEOUT_MS = 60_000; | |
| const SHUTDOWN_TIMEOUT_MS = 30_000; | |
| const MAX_MESSAGE_CHARS = 32 * 1024 * 1024; | |
| export type ComputerHostProcess = { | |
| ready: Promise<ComputerUseCapabilityDescriptor>; | |
| isCurrent(): boolean; | |
| invoke(params: { | |
| command: ComputerHostCommand; | |
| params: Record<string, unknown>; | |
| signal?: AbortSignal; | |
| assertCurrent(): void; | |
| timeoutMs?: number; | |
| sessionKey?: string; | |
| }): Promise<unknown>; | |
| close(execution?: ComputerHostExecutionClose): Promise<void>; | |
| }; | |
| /** The native driver inherits one desktop environment for its entire process lifetime. */ | |
| export function startComputerHostProcess(params: { | |
| env: NodeJS.ProcessEnv; | |
| pluginIds: string[]; | |
| assertCurrent(): void; | |
| supervisor?: ProcessSupervisor; | |
| }): ComputerHostProcess { | |
| const supervisor = params.supervisor ?? getProcessSupervisor(); | |
| const scopeKey = `gateway-computer:${randomUUID()}`; | |
| const cleanupScope = supervisor.acquireScopeCleanup(scopeKey, { processTree: "owned-only" }); | |
| const ready = createDeferredCore<ComputerUseCapabilityDescriptor>(); | |
| const pending = createPendingRequestRegistry<string, unknown, undefined>(); | |
| let run: ManagedRun | undefined; | |
| let closing: Promise<void> | undefined; | |
| let active = true; | |
| let buffer = ""; | |
| let failure: Error | undefined; | |
| const assertActive = () => { | |
| params.assertCurrent(); | |
| if (!active) { | |
| throw failure ?? new Error("Gateway computer process is closed"); | |
| } | |
| }; | |
| const send = (message: ComputerHostInput) => { | |
| if (!run?.stdin || run.stdin.destroyed || run.stdin.writableEnded) { | |
| throw new Error("Gateway computer process input is closed"); | |
| } | |
| run.stdin.write(`${JSON.stringify(message)}\n`, (error) => { | |
| if (error) { | |
| fail(error); | |
| } | |
| }); | |
| }; | |
| const close = (execution?: ComputerHostExecutionClose) => { | |
| if (closing) { | |
| return closing; | |
| } | |
| if (active && run?.activity.resultSettled) { | |
| failure ??= new Error("Gateway computer process exited"); | |
| } | |
| active = false; | |
| const closedError = failure ?? new Error("Gateway computer process is closed"); | |
| ready.reject(closedError); | |
| pending.rejectAll(closedError); | |
| closing = (async () => { | |
| // Launch publishes native custody; readiness can fail while custody still needs cleanup. | |
| await launch; | |
| let exit: RunExit | undefined; | |
| let waitFailure: Error | undefined; | |
| if (run) { | |
| if (!run.activity.resultSettled) { | |
| try { | |
| send({ type: "stop", ...(execution ? { execution } : {}) }); | |
| } catch { | |
| run.cancel("manual-cancel"); | |
| } | |
| } | |
| const timer = setTimeout(() => run?.cancel("manual-cancel"), SHUTDOWN_TIMEOUT_MS); | |
| timer.unref?.(); | |
| try { | |
| exit = await run.wait(); | |
| } catch (error) { | |
| waitFailure = error instanceof Error ? error : new Error(String(error)); | |
| } finally { | |
| clearTimeout(timer); | |
| } | |
| } | |
| await cleanupScope(); | |
| if (waitFailure) { | |
| throw waitFailure; | |
| } | |
| if (exit && (exit.exitCode !== 0 || exit.exitSignal !== null)) { | |
| failure ??= new Error( | |
| `Gateway computer helper shutdown failed (${exit.exitSignal ?? `exit ${exit.exitCode}`})`, | |
| ); | |
| } | |
| if (failure) { | |
| throw new ComputerHostFinalizationError(failure); | |
| } | |
| })(); | |
| void closing.catch((error: unknown) => { | |
| if (!(error instanceof ComputerHostFinalizationError)) { | |
| closing = undefined; | |
| } | |
| }); | |
| return closing; | |
| }; | |
| const fail = (error: Error) => { | |
| failure ??= error; | |
| active = false; | |
| ready.reject(error); | |
| pending.rejectAll(error); | |
| void close().catch(() => {}); | |
| }; | |
| const receive = (chunk: string) => { | |
| buffer += chunk; | |
| if (buffer.length > MAX_MESSAGE_CHARS) { | |
| fail(new Error("Gateway computer response exceeds the transport limit")); | |
| return; | |
| } | |
| let newline: number; | |
| while ((newline = buffer.indexOf("\n")) >= 0) { | |
| const line = buffer.slice(0, newline); | |
| buffer = buffer.slice(newline + 1); | |
| try { | |
| const message = parseComputerHostOutput(JSON.parse(line)); | |
| if (message.type === "ready") { | |
| ready.resolve(parseComputerUseCapabilityDescriptor(message.computerUse)); | |
| } else if (message.type === "result") { | |
| const payload: unknown = JSON.parse(message.payload); | |
| pending.take(message.id)?.resolve(payload); | |
| } else if (message.id) { | |
| pending.take(message.id)?.reject(new Error(message.message)); | |
| } else { | |
| fail(new Error(message.message)); | |
| } | |
| } catch { | |
| fail(new Error("Invalid Gateway computer response")); | |
| } | |
| } | |
| }; | |
| const startupTimer = setTimeout( | |
| () => fail(new Error("Gateway computer provider startup timed out")), | |
| STARTUP_TIMEOUT_MS, | |
| ); | |
| startupTimer.unref?.(); | |
| void ready.promise.then( | |
| () => clearTimeout(startupTimer), | |
| () => clearTimeout(startupTimer), | |
| ); | |
| // Return cleanup custody before deferred admission can acquire a native process. | |
| const launch = Promise.resolve().then(async () => { | |
| if (!active) { | |
| return; | |
| } | |
| try { | |
| const node = resolveExecutablePath("node", { env: params.env }); | |
| if (!node) { | |
| throw new Error("Gateway computer control requires Node.js in PATH"); | |
| } | |
| const worker = resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.computerHost); | |
| assertActive(); | |
| run = await supervisor.spawn({ | |
| scopeKey, | |
| mode: "child", | |
| argv: [node, ...resolveRuntimeWorkerArgv(worker, node)], | |
| env: params.env, | |
| exactEnv: true, | |
| stdinMode: "pipe-open", | |
| captureOutput: false, | |
| onStdout: receive, | |
| assertCurrent: assertActive, | |
| }); | |
| void run.wait().then( | |
| () => { | |
| if (!closing) { | |
| fail(new Error("Gateway computer process exited")); | |
| } | |
| }, | |
| (error: unknown) => { | |
| if (!closing) { | |
| fail(error instanceof Error ? error : new Error(String(error))); | |
| } | |
| }, | |
| ); | |
| assertActive(); | |
| send({ type: "start", pluginIds: params.pluginIds }); | |
| } catch (error) { | |
| if (!closing) { | |
| fail(error instanceof Error ? error : new Error(String(error))); | |
| } | |
| } | |
| }); | |
| return { | |
| ready: ready.promise, | |
| isCurrent: () => active, | |
| async invoke(request) { | |
| await ready.promise; | |
| assertActive(); | |
| request.assertCurrent(); | |
| request.signal?.throwIfAborted(); | |
| const id = randomUUID(); | |
| let timedOut = false; | |
| const cancel = () => { | |
| try { | |
| send({ type: "cancel", id }); | |
| } catch {} | |
| }; | |
| const abort = () => { | |
| cancel(); | |
| pending | |
| .take(id) | |
| ?.reject(request.signal?.reason ?? new Error("Computer invocation cancelled")); | |
| }; | |
| const entry = pending.add(id, { | |
| value: undefined, | |
| timeoutMs: request.timeoutMs ?? 60_000, | |
| timeoutError: () => new Error("Gateway computer invocation timed out"), | |
| onTimeout: () => { | |
| timedOut = true; | |
| cancel(); | |
| }, | |
| dispose: () => request.signal?.removeEventListener("abort", abort), | |
| }); | |
| if (!entry) { | |
| throw new Error("Gateway computer request already exists"); | |
| } | |
| request.signal?.addEventListener("abort", abort, { once: true }); | |
| try { | |
| assertActive(); | |
| request.assertCurrent(); | |
| request.signal?.throwIfAborted(); | |
| send({ | |
| type: "invoke", | |
| id, | |
| command: request.command, | |
| paramsJSON: JSON.stringify(request.params), | |
| ...(request.sessionKey ? { sessionKey: request.sessionKey } : {}), | |
| }); | |
| } catch (error) { | |
| pending.take(id)?.reject(error); | |
| } | |
| try { | |
| return await entry.promise; | |
| } catch (error) { | |
| if (request.signal?.aborted || timedOut || !active) { | |
| await close(); | |
| } | |
| throw error; | |
| } | |
| }, | |
| close, | |
| }; | |
| } | |