UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

138 lines (137 loc) • 5.36 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import "./computer-use-contract-BA1SbL9L.mjs"; import { randomUUID } from "node:crypto"; //#region src/plugins/computer-use-registration.ts /** Register the canonical node-host command pair for one node-local provider. */ function registerComputerUseProvider(api, provider) { let execution; let closingPromise; let pendingClose; const hasActiveWork = () => execution !== void 0 || closingPromise !== void 0 || pendingClose !== void 0; const executionEnvelopeFromParams = (paramsJSON) => { let value; try { value = JSON.parse(paramsJSON ?? "{}"); } catch { throw new Error("COMPUTER_INVALID_REQUEST: params must be valid JSON"); } const executionId = isRecord(value) ? value.executionId : void 0; if (executionId === void 0) return { executionId: void 0, value }; if (typeof executionId !== "string" || !(/* @__PURE__ */ new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "u")).test(executionId)) throw new Error("COMPUTER_INVALID_REQUEST: executionId is required"); return { executionId, value }; }; const getExecution = async (executionId, context) => { for (let barrier = closingPromise; barrier !== void 0; barrier = closingPromise) await barrier; if (execution && execution.id !== executionId) throw new Error("COMPUTER_HOST_BUSY: another provider execution owns this computer"); if (!execution) { const opened = provider.openExecution(context?.sessionKey ? { executionId, sessionKey: context.sessionKey } : { executionId }); opened.catch(() => { if (execution?.promise === opened) execution = void 0; }); execution = { id: executionId, promise: opened }; } return execution.promise; }; const closeCurrentExecution = (executionId, reason) => { const current = execution; if (!current || executionId !== void 0 && current.id !== executionId) return Promise.resolve(); if (pendingClose) return pendingClose; const close = current.promise.then(async (opened) => await opened.close(reason)); pendingClose = close; closingPromise = close; close.then(() => { if (execution === current) execution = void 0; pendingClose = void 0; closingPromise = void 0; }, () => { pendingClose = void 0; if (execution !== current) closingPromise = void 0; }); return close; }; const closeExecution = (executionId, reason) => { if (!pendingClose) return closeCurrentExecution(executionId, reason); const joined = (async () => { for (let barrier = pendingClose; barrier !== void 0; barrier = pendingClose) { const matchesClosingOwner = executionId === void 0 || execution?.id === executionId; try { await barrier; } catch (error) { if (matchesClosingOwner) throw error; return; } } await closeCurrentExecution(executionId, reason); })(); joined.catch(() => {}); return joined; }; api.registerNodeHostCommand({ command: "screen.snapshot", cap: "screen", dangerous: false, prepare: (context) => provider.prepare?.(context), isAvailable: () => provider.isAvailable(), hasActiveWork, watchAvailability: (context, onChange) => { const stopWatching = provider.watchAvailability?.(context, onChange); let availabilityStop; return async () => { availabilityStop ??= Promise.resolve().then(() => stopWatching?.()).catch((error) => { availabilityStop = void 0; throw error; }); const failures = (await Promise.allSettled([availabilityStop, closeExecution(void 0, "node-host-stop")])).flatMap((result) => result.status === "rejected" ? [result.reason] : []); if (failures.length === 1) throw failures[0]; if (failures.length > 1) throw new AggregateError(failures, "computer provider shutdown failed"); }; }, onDisconnect: async () => await closeExecution(void 0, "gateway-disconnect"), handle: async (paramsJSON, _io, context) => { const envelope = executionEnvelopeFromParams(paramsJSON); if (envelope.executionId) return await (await getExecution(envelope.executionId, context)).snapshot(paramsJSON, context?.signal); const executionId = randomUUID(); const opened = await provider.openExecution(context?.sessionKey ? { executionId, sessionKey: context.sessionKey } : { executionId }); try { return await opened.snapshot(paramsJSON, context?.signal); } finally { await opened.close("snapshot-complete"); } } }); api.registerNodeHostCommand({ command: "computer.act", cap: "computer", dangerous: true, computerUse: () => provider.capabilities(), isAvailable: () => provider.isAvailable(), hasActiveWork, handle: async (paramsJSON, _io, context) => { const envelope = executionEnvelopeFromParams(paramsJSON); if (!envelope.executionId) throw new Error("COMPUTER_INVALID_REQUEST: executionId is required"); if (isRecord(envelope.value) && envelope.value.action === "__close_execution") { const reason = envelope.value.reason; await closeExecution(envelope.executionId, typeof reason === "string" && reason.trim() ? reason.slice(0, 64) : "completion"); return JSON.stringify({ ok: true }); } return await (await getExecution(envelope.executionId, context)).act(paramsJSON, context?.signal); } }); } //#endregion export { registerComputerUseProvider as t };