openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
261 lines (260 loc) • 11.7 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { Dn as validateNodeDescribeParams, Nn as validateNodeListParams, Vn as validateNodePluginToolsUpdateParams, Wn as validateNodeSkillsUpdateParams } from "./src-l2dWvEsN.mjs";
import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs";
import { f as errorShape } from "./error-codes-Dh56LniQ.mjs";
import { i as listDevicePairing } from "./device-pairing-Drmq_Czv.mjs";
import { i as recordRemoteNodeInfo, o as refreshRemoteNodeBins, u as updatePairedNodeSessionHost } from "./remote-B1XF-V4S.mjs";
import { n as projectNodePairing } from "./device-pairing-node.records-Fl7vnrHF.mjs";
import { a as listNodePairing } from "./device-pairing-node-HEhf89hA.mjs";
import { n as NODE_RUNNER_UPDATE_REQUIRED_ISSUE, o as formatNodeRunnerInventoryIssue, s as parseNodeRunnerInventoryDeclaration } from "./node-runner-inventory-BGg-lRhv.mjs";
import { d as updateNodeRunnerInventory, t as collectNodeCatalogRuntimeState } from "./node-registry-private-CyaYRwVd.mjs";
import { f as refreshClientPluginNodeCapability, i as hasAuthorizedClientPluginNodeCapabilityUrl, l as pluginNodeCapabilityScopedHostUrlsConflict } from "./plugin-node-capability-BlHl5bub.mjs";
import { t as assertValidParams } from "./validation-Bu27N0nv.mjs";
import { n as respondUnavailableOnThrow } from "./response-D1p0KfaW.mjs";
import { o as projectPairedDeviceNodeBindings } from "./device-pairing-node-state-BWkfmloU.mjs";
import { n as getKnownNode, r as listKnownNodes, t as createKnownNodeCatalog } from "./node-catalog-BIBvC887.mjs";
import { t as nodeInvokePolicy } from "./nodes-policy-fOzIq_sI.mjs";
import { t as resolveLocalNodeId } from "./local-id-CNgfsz_n.mjs";
//#region src/gateway/server-methods/nodes.read.ts
function safeNodeReadProjection(node, ownDeviceId) {
if (!node.paired && !node.connected) return null;
const { pendingRequestId, pendingDeclaredCaps: _pendingDeclaredCaps, pendingDeclaredCommands: _pendingDeclaredCommands, pendingDeclaredPermissions: _pendingDeclaredPermissions, ...safeNode } = node;
return node.nodeId === ownDeviceId && pendingRequestId ? {
...safeNode,
pendingRequestId
} : safeNode;
}
function respondRunnerInventoryRetry(respond, message) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, message));
}
async function listNodesForClient(params) {
const devicePairing = await listDevicePairing();
const nodePairing = projectNodePairing(devicePairing.paired);
const connectedNodes = params.context.nodeRegistry.listConnectedForPairingStates(projectPairedDeviceNodeBindings(devicePairing.paired));
const runtimeState = collectNodeCatalogRuntimeState(params.context.nodeRegistry, connectedNodes);
const catalog = createKnownNodeCatalog({
pairedDevices: devicePairing.paired,
pairedNodes: nodePairing.paired,
pendingNodes: nodePairing.pending,
connectedNodes,
...runtimeState
});
const localNodeId = await resolveLocalNodeId().catch((error) => {
params.context.logGateway.warn(`failed to resolve same-install node-host identity: ${formatErrorMessage(error)}`);
return null;
});
const nodes = (params.nodeId ? [getKnownNode(catalog, params.nodeId)].filter((node) => node !== null) : listKnownNodes(catalog)).map((node) => node.nodeId === localNodeId ? Object.assign({}, node, { gatewayLocal: true }) : node);
if (nodeInvokePolicy.canReadPendingNodePairing(params.client)) return {
nodes,
connectedNodes
};
const ownDeviceId = normalizeOptionalString(params.client?.connect?.device?.id);
return {
nodes: nodes.map((node) => safeNodeReadProjection(node, ownDeviceId)).filter((node) => node !== null),
connectedNodes
};
}
function normalizePluginSurfaceRefreshParams(params) {
if (!params || typeof params !== "object") return;
const surface = normalizeOptionalString(params.surface);
if (!surface) return;
const observedUrl = normalizeOptionalString(params.observedUrl);
return {
surface,
...observedUrl ? { observedUrl } : {}
};
}
const handlePluginSurfaceRefresh = ({ params, respond, client }) => {
const parsed = normalizePluginSurfaceRefreshParams(params);
if (!parsed) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "surface required"));
return;
}
const { surface, observedUrl } = parsed;
const currentUrl = client?.pluginSurfaceUrls?.[surface];
const capabilitySurface = client?.pluginNodeCapabilitySurfaces?.[surface] ?? { surface };
if (client && currentUrl && observedUrl && pluginNodeCapabilityScopedHostUrlsConflict(currentUrl, observedUrl) && hasAuthorizedClientPluginNodeCapabilityUrl({
client,
surface: capabilitySurface,
url: currentUrl
})) {
respond(true, {
surface,
pluginSurfaceUrls: { [surface]: currentUrl }
}, void 0);
return;
}
const refreshed = client ? refreshClientPluginNodeCapability({
client,
surface: capabilitySurface
}) : void 0;
if (!refreshed) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, `${surface} plugin surface unavailable`));
return;
}
respond(true, {
surface: refreshed.surface,
pluginSurfaceUrls: { [refreshed.surface]: refreshed.scopedUrl },
expiresAtMs: refreshed.expiresAtMs
}, void 0);
};
function refreshConnectedNodeSurfaceCaches(params) {
const cfg = params.cfg ?? params.context.getRuntimeConfig();
const { nodeSession } = params;
recordRemoteNodeInfo({
nodeId: nodeSession.nodeId,
connId: nodeSession.connId,
displayName: nodeSession.displayName,
platform: nodeSession.platform,
deviceFamily: nodeSession.deviceFamily,
commands: nodeSession.commands,
remoteIp: nodeSession.remoteIp,
pairingGeneration: nodeSession.pairingGeneration
});
refreshRemoteNodeBins({
nodeId: nodeSession.nodeId,
platform: nodeSession.platform,
deviceFamily: nodeSession.deviceFamily,
commands: nodeSession.commands,
cfg
}).catch((err) => params.context.logGateway.warn(`remote bin probe failed for ${nodeSession.nodeId}: ${formatErrorMessage(err)}`));
}
const nodeReadHandlers = {
"node.list": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateNodeListParams, "node.list", respond)) return;
await respondUnavailableOnThrow(respond, async () => {
const { nodes, connectedNodes } = await listNodesForClient({
client,
context
});
const activeNodeId = context.nodeRegistry.getActiveNode(connectedNodes)?.nodeId;
const nodesWithPresence = activeNodeId ? nodes.map((node) => node.nodeId === activeNodeId ? {
...node,
active: true
} : node) : nodes;
respond(true, {
ts: Date.now(),
activeNodeId,
nodes: nodesWithPresence
}, void 0);
});
},
"node.describe": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateNodeDescribeParams, "node.describe", respond)) return;
const { nodeId } = params;
const id = normalizeOptionalString(nodeId) ?? "";
if (!id) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
}
await respondUnavailableOnThrow(respond, async () => {
const { nodes, connectedNodes } = await listNodesForClient({
client,
context,
nodeId: id
});
const node = nodes[0];
if (!node) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "unknown nodeId"));
return;
}
respond(true, {
ts: Date.now(),
...node,
...context.nodeRegistry.getActiveNode(connectedNodes)?.nodeId === id ? { active: true } : {}
}, void 0);
});
},
"plugin.surface.refresh": handlePluginSurfaceRefresh,
"node.pluginSurface.refresh": handlePluginSurfaceRefresh,
"node.pluginTools.update": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateNodePluginToolsUpdateParams, "node.pluginTools.update", respond)) return;
const nodeId = normalizeOptionalString(client?.connect?.device?.id ?? client?.connect?.client?.id);
if (!nodeId) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
}
const updated = context.nodeRegistry.updateNodePluginTools(nodeId, client?.connId, params.tools);
if (!updated) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "unknown nodeId"));
return;
}
respond(true, {
nodeId,
tools: updated.nodePluginTools
}, void 0);
},
"node.skills.update": async ({ params, respond, client, context }) => {
if (!assertValidParams(params, validateNodeSkillsUpdateParams, "node.skills.update", respond)) return;
const nodeId = normalizeOptionalString(client?.connect?.device?.id ?? client?.connect?.client?.id);
if (!nodeId) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
return;
}
const updated = context.nodeRegistry.updateNodeSkills(nodeId, client?.connId, params.skills);
if (!updated) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "unknown nodeId"));
return;
}
respond(true, {
nodeId,
skills: updated.nodeSkills
}, void 0);
},
"node.runnerInventory.update": async ({ params, respond, client, context }) => {
const declaration = parseNodeRunnerInventoryDeclaration(params);
if (!declaration) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid node runner inventory"));
return;
}
const nodeId = normalizeOptionalString(client?.connect?.device?.id);
const updated = nodeId ? updateNodeRunnerInventory({
registry: context.nodeRegistry,
nodeId,
connId: client?.connId,
declaration
}) : null;
if (!nodeId || !updated) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "unknown nodeId"));
return;
}
if (declaration.protocolFeatures.length === 1 && declaration.protocolFeatures[0] !== "node-worker-supervisor-v6") {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, formatNodeRunnerInventoryIssue(nodeId, NODE_RUNNER_UPDATE_REQUIRED_ISSUE)));
return;
}
const connId = client?.connId;
const currentSession = context.nodeRegistry.get(nodeId);
const pairingGeneration = currentSession && currentSession.connId === connId ? currentSession.pairingGeneration : void 0;
if (!connId || !pairingGeneration) {
const pendingSurface = (await listNodePairing()).pending.find((entry) => entry.nodeId === nodeId);
respondRunnerInventoryRetry(respond, pendingSurface ? `node capability surface is awaiting operator approval; run \`openclaw nodes approve ${pendingSurface.requestId}\` (see \`openclaw nodes pending\`), then this node retries automatically` : "node runner inventory publication is not current; retry after pairing completes");
return;
}
const sessionHost = "workerHost" in declaration && declaration.workerHost.enabled;
try {
if (!await updatePairedNodeSessionHost({
nodeId,
sessionHost,
expectedPairingGeneration: {
nodeId,
key: pairingGeneration
},
isConnectionCurrent: () => {
const current = context.nodeRegistry.get(nodeId);
return current?.connId === connId && current.pairingGeneration === pairingGeneration && current.client.invalidated !== true;
}
})) {
respondRunnerInventoryRetry(respond, "node runner inventory publication lost its pairing ownership; retry");
return;
}
} catch (error) {
context.logGateway.warn(`failed to persist runner host consent for ${nodeId}: ${formatErrorMessage(error)}`);
respondRunnerInventoryRetry(respond, "node runner inventory persistence failed; retry");
return;
}
respond(true, { nodeId }, void 0);
}
};
//#endregion
export { refreshConnectedNodeSurfaceCaches as n, nodeReadHandlers as t };