UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

404 lines (403 loc) 18.2 kB
import { t as getActivePluginGatewayNodePolicyRegistry } from "./runtime-state-Ccxn1fO9.js"; import { v as NODE_WORKER_DESKTOP_COMPUTER_COMMAND } from "./node-commands-BC8PhxqU.js"; import { T as validateAgentRunDelegatedAuthority, s as getActiveAgentRunDelegatedAuthority } from "./agent-run-registry-CKYKdfNd.js"; import { m as parseComputerUseCapabilityDescriptor } from "./computer-use-contract-CisZRwLE.js"; import { l as resolveNodeCommandAllowlist, o as isNodeCommandAllowed } from "./node-command-policy-H4FpS0aA.js"; import { n as parseNodeWorkerComputerInput } from "./node-computer-protocol-BAxblUsb.js"; import { n as WorkerRunnerUnavailableError } from "./tunnel-contract-CB5bcNwX.js"; import { n as invokeNodeWithReadinessRetry, t as applyPluginNodeInvokePolicy } from "./node-invoke-plugin-policy-C4Da33zu.js"; import { randomUUID } from "node:crypto"; //#region src/gateway/worker-environments/computer-transport.ts const COMPUTER_COMMANDS = ["screen.snapshot", "computer.act"]; function payload(result) { if (!result.ok) throw new Error(result.error?.message ?? "Session desktop command failed"); return result.payloadJSON ? JSON.parse(result.payloadJSON) : result.payload; } /** Captures one placement's desktop; neither model input nor a copied run ID selects a node. */ function createWorkerComputerTransportOwner(options) { return async (claim) => { const placement = options.placements.get(claim.sessionId); if (placement?.state !== "active" || !options.placements.validateTurnClaim(claim)) throw new Error("Session desktop placement is no longer active"); const environment = options.store.get(placement.environmentId); if (!environment?.nodeDeviceId || !environment.desktop && !environment.sharedHost) return; const context = options.resolveGatewayContext(); const nodeTransport = options.getNodeTransport(); if (!context || !nodeTransport) throw new Error("Session desktop Gateway is unavailable"); const node = context.nodeRegistry.get(environment.nodeDeviceId); if (!node) throw new WorkerRunnerUnavailableError(); const environmentIsCurrent = () => { const current = options.store.get(environment.environmentId); const currentNode = context.nodeRegistry.get(node.nodeId); return options.resolveGatewayContext() === context && options.getNodeTransport() === nodeTransport && current?.leaseId === environment.leaseId && current?.ownerEpoch === environment.ownerEpoch && current?.nodeDeviceId === node.nodeId && currentNode?.connId === node.connId && currentNode?.pairingGeneration === node.pairingGeneration && currentNode.client.invalidated !== true; }; const placementIsCurrent = () => { const current = options.placements.get(claim.sessionId); const currentEnvironment = options.store.get(environment.environmentId); return environmentIsCurrent() && options.placements.validateTurnClaim(claim) && current?.state === "active" && current.generation === claim.placementGeneration && current.sessionKey === placement.sessionKey && current.agentId === placement.agentId && current.environmentId === environment.environmentId && current.activeOwnerEpoch === environment.ownerEpoch && currentEnvironment?.state === "attached" && currentEnvironment.destroyRequestedAtMs === null && currentEnvironment.attachedSessionIds.length === 1 && currentEnvironment.attachedSessionIds[0] === claim.sessionId; }; const assertPlacement = () => { if (!placementIsCurrent()) throw new Error("Session desktop placement authority changed"); }; assertPlacement(); const privateNode = environment.sharedHost ? void 0 : (await nodeTransport.listCurrentNodes()).find((candidate) => candidate.nodeId === node.nodeId); assertPlacement(); if (!environment.sharedHost && !privateNode) throw new Error("Session desktop node lacks the current private worker protocol"); let computerUse = node.computerUse; if (privateNode) { const result = await nodeTransport.invoke({ node: privateNode, command: NODE_WORKER_DESKTOP_COMPUTER_COMMAND, params: { operation: "capabilities" }, isDispatchAuthorized: placementIsCurrent }); assertPlacement(); if (!nodeTransport.isCurrent(privateNode)) throw new Error("Session desktop private node owner changed"); if (!result.ok) { options.warn("Session computer control is unavailable; enable the desktop provider and reprovision the worker."); return; } computerUse = parseComputerUseCapabilityDescriptor(payload(result)); } else if (!COMPUTER_COMMANDS.every((command) => node.commands.includes(command))) return; if (!computerUse) return; const registry = getActivePluginGatewayNodePolicyRegistry(); if (privateNode && !registry?.nodeInvokePolicies.some((entry) => entry.policy.commands.includes("computer.act"))) { options.warn("Session computer control is unavailable; enable its provider plugin on the Gateway."); return; } const policyOwners = COMPUTER_COMMANDS.map((command) => { const entry = registry?.nodeInvokePolicies.find((item) => item.policy.commands.includes(command)); const policy = entry?.policy; const plugin = registry?.plugins.find((item) => item.id === entry?.pluginId); return () => registry?.nodeInvokePolicies.find((item) => item.policy.commands.includes(command)) === entry && entry?.policy === policy && (!plugin || registry?.plugins.includes(plugin) && plugin.enabled && plugin.status === "loaded"); }); const descriptor = { nodeId: node.nodeId, computerUse }; const providerGeneration = computerUse.provider.generation; let closed = false; let closing; const activeBindings = /* @__PURE__ */ new Set(); const resourceBindingIsCurrent = () => environmentIsCurrent() && (!privateNode || nodeTransport.isCurrent(privateNode)); const bindingIsCurrent = () => resourceBindingIsCurrent() && getActivePluginGatewayNodePolicyRegistry() === registry && policyOwners.every((isCurrent) => isCurrent()) && (privateNode !== void 0 || context.nodeRegistry.get(node.nodeId)?.computerUse?.provider.generation === providerGeneration); const parseRequest = (request) => { if (request.nodeId !== node.nodeId) throw new Error("Computer control is bound to this session's desktop"); const close = request.command === "computer.act" && request.commandParams.action === "__close_execution"; const input = parseNodeWorkerComputerInput(JSON.stringify(close ? { operation: "close", executionId: request.commandParams.executionId, reason: request.commandParams.reason } : { operation: request.command === "screen.snapshot" ? "snapshot" : "act", providerGeneration, params: request.commandParams })); if (input.operation === "capabilities") throw new Error("Session computer cannot request another capability probe"); return input; }; const send = async (input, params) => { const isCurrent = () => resourceBindingIsCurrent() && params.isDispatchAuthorized(); if (!isCurrent()) throw new Error("Session computer authority closed before dispatch"); const command = input.operation === "snapshot" ? "screen.snapshot" : "computer.act"; const commandParams = input.operation === "close" ? { action: "__close_execution", executionId: input.executionId, reason: input.reason } : input.params; return privateNode ? await nodeTransport.invoke({ node: privateNode, command: NODE_WORKER_DESKTOP_COMPUTER_COMMAND, params: input, ...params, isDispatchAuthorized: isCurrent }) : await invokeNodeWithReadinessRetry(input.operation === "close" ? { invoke: (request) => context.nodeRegistry.invokeLifecycle({ ...request, isDispatchAuthorized: isCurrent }) } : context.nodeRegistry, { nodeId: node.nodeId, expectedConnId: node.connId, expectedPairingGeneration: node.pairingGeneration, command, params: commandParams, sessionKey: placement.sessionKey, ...params, isDispatchAuthorized: isCurrent }); }; return { descriptor, bind(operationalRunInstance) { const authority = getActiveAgentRunDelegatedAuthority(operationalRunInstance); if (!authority || operationalRunInstance.runId !== claim.runId) throw new Error("Session computer requires the exact admitted run"); const identity = { kind: "agentRuntime", agentId: placement.agentId, sessionKey: placement.sessionKey, operationalRunInstance, delegatedAuthority: claim.owner.kind === "worker" ? { ...authority, kind: "worker", turnClaim: claim } : { ...authority, kind: "local" } }; let execution; let bindingClosed = false; let bindingClosing; const inFlight = /* @__PURE__ */ new Set(); const lifetime = new AbortController(); const assertCurrent = () => { if (closed || bindingClosed || !bindingIsCurrent() || !validateAgentRunDelegatedAuthority(authority)) throw new Error("Session computer run authority closed"); assertPlacement(); }; assertCurrent(); const execute = async (input, request, assertAuthorized) => { const assertInvocationCurrent = () => { assertCurrent(); assertAuthorized?.(); }; const command = input.operation === "snapshot" ? "screen.snapshot" : "computer.act"; const commandParams = input.params; const isCurrent = () => { try { assertInvocationCurrent(); return true; } catch { return false; } }; assertInvocationCurrent(); const signal = request.signal ? AbortSignal.any([request.signal, lifetime.signal]) : lifetime.signal; const dispatch = async (params) => { const actual = parseNodeWorkerComputerInput(JSON.stringify({ ...input, params: params.params })); if (actual.operation === "capabilities" || actual.operation === "close" || actual.params.executionId !== execution?.physicalId) throw new Error("Computer policy cannot replace the session execution owner"); return await send(actual, { timeoutMs: params.timeoutMs, signal: params.signal, idempotencyKey: params.idempotencyKey, isDispatchAuthorized: () => isCurrent() && params.isDispatchAuthorized(), onDispatchReady: params.onDispatchReady }); }; const commandIsAllowed = () => { const currentNode = context.nodeRegistry.get(node.nodeId); const declaredCommands = privateNode ? [...COMPUTER_COMMANDS] : currentNode?.commands ?? []; return isNodeCommandAllowed({ command, declaredCommands, allowlist: resolveNodeCommandAllowlist(context.getRuntimeConfig(), { ...currentNode, approvedCommands: declaredCommands }) }).ok; }; const result = await applyPluginNodeInvokePolicy({ context, client: null, agentRuntimeIdentity: identity, nodeSession: node, command, params: commandParams, sessionKey: placement.sessionKey, timeoutMs: request.timeoutMs, idempotencyKey: request.idempotencyKey, signal, isInvocationCurrent: isCurrent, isApprovalAuthorityActive: isCurrent, privateTransport: { ...privateNode ? { commands: COMPUTER_COMMANDS } : {}, isCurrent, invoke: dispatch } }); assertInvocationCurrent(); if (result) { if (!result.ok) throw new Error(result.message ?? "Session computer action denied"); return result.payloadJSON ? JSON.parse(result.payloadJSON) : result.payload; } if (privateNode && command === "computer.act" || !commandIsAllowed()) throw new Error("Session computer command has no active policy or permission"); const raw = await dispatch({ params: commandParams, timeoutMs: request.timeoutMs, signal, idempotencyKey: request.idempotencyKey, isDispatchAuthorized: () => isCurrent() && commandIsAllowed() }); assertInvocationCurrent(); return payload(raw); }; const binding = { close(reason) { if (bindingClosing) return bindingClosing; bindingClosed = true; lifetime.abort(); bindingClosing = (async () => { await Promise.allSettled(inFlight); if (!execution || !resourceBindingIsCurrent()) { activeBindings.delete(binding); return { ok: true }; } const result = await send({ operation: "close", executionId: execution.physicalId, reason: reason.slice(0, 64) }, { isDispatchAuthorized: resourceBindingIsCurrent }); if (!resourceBindingIsCurrent()) throw new Error("Session computer cleanup owner changed"); const output = payload(result); activeBindings.delete(binding); return output; })(); return bindingClosing; } }; return { computerUse, async resolveNode(query, signal) { signal?.throwIfAborted(); assertCurrent(); if (query !== void 0 && query !== node.nodeId) throw new Error("Computer control is bound to this session's desktop"); return descriptor; }, async invoke(request, assertAuthorized) { const input = parseRequest(request); const logicalId = input.operation === "close" ? input.executionId : input.params.executionId; if (execution && logicalId !== execution.logicalId) throw new Error("Session computer execution owner changed"); if (input.operation === "close") return binding.close(input.reason); assertCurrent(); request.signal?.throwIfAborted(); execution ??= { logicalId, physicalId: randomUUID() }; activeBindings.add(binding); input.params.executionId = execution.physicalId; const operation = execute(input, request, assertAuthorized); inFlight.add(operation); operation.finally(() => inFlight.delete(operation)).catch(() => {}); return operation; } }; }, close(reason) { if (closing) return closing; closed = true; closing = (async () => { const failures = (await Promise.allSettled([...activeBindings].map((binding) => binding.close(reason)))).filter((result) => result.status === "rejected"); if (failures.length) throw new AggregateError(failures.map((failure) => failure.reason), "Session computer cleanup failed"); })(); return closing; } }; }; } function createWorkerComputerService(options) { const create = createWorkerComputerTransportOwner(options); const owners = /* @__PURE__ */ new Map(); const closeOwner = (owner, reason) => { if (owner.closing) return owner.closing; owner.transport = void 0; owner.connection?.signal.removeEventListener("abort", owner.connection.abort); owner.closing = (async () => { try { await owner.prepared; await owner.closeComputer?.(reason); } finally { if (owners.get(owner.claimId) === owner) owners.delete(owner.claimId); } })(); return owner.closing; }; const unregister = options.placements.registerTurnClaimClosedHandler((claim) => { const owner = owners.get(claim.claimId); if (owner) closeOwner(owner, "turn-closed").catch(() => options.warn("Session computer cleanup failed after turn closure.")); }); let stopped = false; return { prepare: (claim) => { if (stopped || !options.placements.validateTurnClaim(claim)) return Promise.reject(/* @__PURE__ */ new Error("Session computer owner closed")); const prior = owners.get(claim.claimId); if (prior) return prior.prepared; const prepared = create(claim).then((computer) => { if (!computer) return; owner.closeComputer = (reason) => computer.close(reason); const assertOwner = () => { if (stopped || owner.closing || owners.get(claim.claimId) !== owner) throw new Error("Session computer owner replaced"); }; return { ...computer, bind(run) { assertOwner(); const transport = computer.bind(run); const bound = { computerUse: transport.computerUse, async resolveNode(query, signal) { assertOwner(); const result = await transport.resolveNode(query, signal); assertOwner(); return result; }, async invoke(request, assertAuthorized) { assertOwner(); const result = await transport.invoke(request, () => { assertOwner(); assertAuthorized?.(); }); assertOwner(); return result; } }; owner.transport = bound; return bound; }, close: (reason) => closeOwner(owner, reason) }; }); const owner = { claimId: claim.claimId, prepared }; owners.set(claim.claimId, owner); return prepared; }, execute: (async ({ identity, request, signal, assertCurrent }) => { assertCurrent(); const claim = identity.turnClaim; const owner = claim ? owners.get(claim.claimId) : void 0; const computer = await owner?.prepared; assertCurrent(); if (!computer || !owner?.transport || owner.closing || owners.get(owner.claimId) !== owner || !signal || owner.connection && owner.connection.signal !== signal) throw new Error("Session computer connection is unavailable; start a new turn"); signal.throwIfAborted(); if (!owner.connection) { const abort = () => { closeOwner(owner, "worker-disconnect").catch(() => options.warn("Session computer cleanup failed after worker disconnect.")); }; owner.connection = { signal, abort }; signal.addEventListener("abort", abort, { once: true }); } const result = await owner.transport.invoke({ nodeId: computer.descriptor.nodeId, command: request.command, commandParams: JSON.parse(request.paramsJson), timeoutMs: request.timeoutMs, idempotencyKey: request.idempotencyKey, signal }, assertCurrent); assertCurrent(); return { resultJson: JSON.stringify(result) }; }), close: async () => { stopped = true; unregister(); const failures = (await Promise.allSettled([...owners.values()].map((owner) => closeOwner(owner, "gateway-stop")))).filter((result) => result.status === "rejected"); if (failures.length) throw new AggregateError(failures.map((failure) => failure.reason), "Session computer cleanup failed"); } }; } //#endregion export { createWorkerComputerService, createWorkerComputerTransportOwner };