openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
423 lines (422 loc) • 16.1 kB
JavaScript
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { c as normalizeOptionalLowercaseString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { h as normalizeUniqueStringEntries } from "./string-normalization-DsCfAx8q.js";
import { t as getActivePluginGatewayNodePolicyRegistry } from "./runtime-state-Ccxn1fO9.js";
import { C as NODE_WORKER_PRIVATE_COMMANDS, M as isPrivateNodeInvokeCommand, a as NODE_EXEC_APPROVALS_COMMANDS, c as NODE_MCP_TOOLS_CALL_COMMAND, m as NODE_SYSTEM_RUN_COMMANDS, n as NODE_BROWSER_PROXY_COMMANDS, o as NODE_FILE_COMMANDS, p as NODE_SYSTEM_NOTIFY_COMMAND, r as NODE_DEVICE_APPS_COMMAND, t as NODE_AGENT_CLI_CLAUDE_RUN_COMMAND } from "./node-commands-BC8PhxqU.js";
import { n as NODE_DESKTOP_STREAM_COMMAND } from "./node-desktop-stream-BZM2AiRA.js";
//#region src/gateway/device-metadata-normalization.ts
function normalizeTrimmedMetadata(value) {
if (typeof value !== "string") return "";
const trimmed = value.trim();
return trimmed ? trimmed : "";
}
/** Normalize device metadata for policy classification. */
function normalizeDeviceMetadataForPolicy(value) {
const trimmed = normalizeTrimmedMetadata(value);
if (!trimmed) return "";
return normalizeLowercaseStringOrEmpty(trimmed.normalize("NFKD").replace(/\p{M}/gu, ""));
}
//#endregion
//#region src/gateway/node-command-policy-mobile.ts
const NOTIFICATION_COMMANDS = ["notifications.list"];
const MOBILE_NODE_COMMANDS = {
location: ["location.get"],
notification: NOTIFICATION_COMMANDS,
androidNotification: [...NOTIFICATION_COMMANDS, "notifications.actions"],
device: ["device.info", "device.status"]
};
//#endregion
//#region src/gateway/node-command-policy.ts
const CAMERA_COMMANDS = ["camera.list"];
const MAC_CAMERA_COMMANDS = ["camera.ptz.status"];
const CAMERA_DANGEROUS_COMMANDS = [
"camera.snap",
"camera.clip",
"camera.ptz.control"
];
const SCREEN_COMMANDS = ["screen.snapshot"];
const SCREEN_DANGEROUS_COMMANDS = ["screen.record", NODE_DESKTOP_STREAM_COMMAND];
const COMPUTER_COMMANDS = ["computer.act"];
const MOBILE_UI_COMMANDS = ["mobile.ui.observe", "mobile.ui.act"];
const ANDROID_DEVICE_COMMANDS = [
...MOBILE_NODE_COMMANDS.device,
"device.permissions",
"device.health",
NODE_DEVICE_APPS_COMMAND
];
const CONTACTS_COMMANDS = ["contacts.search"];
const CONTACTS_DANGEROUS_COMMANDS = ["contacts.add"];
const CALENDAR_COMMANDS = ["calendar.events"];
const CALENDAR_DANGEROUS_COMMANDS = ["calendar.add"];
const CALL_LOG_COMMANDS = ["callLog.search"];
const REMINDERS_COMMANDS = ["reminders.list"];
const REMINDERS_DANGEROUS_COMMANDS = ["reminders.add"];
const PHOTOS_COMMANDS = ["photos.latest"];
const MOTION_COMMANDS = ["motion.activity", "motion.pedometer"];
const HEALTH_DANGEROUS_COMMANDS = ["health.summary"];
const SMS_DANGEROUS_COMMANDS = ["sms.send", "sms.search"];
const TALK_PTT_COMMANDS = [
"talk.ptt.start",
"talk.ptt.stop",
"talk.ptt.cancel",
"talk.ptt.once"
];
const IOS_WATCH_RELAY_COMMANDS = ["watch.status", "watch.notify"];
const IOS_SYSTEM_COMMANDS = [NODE_SYSTEM_NOTIFY_COMMAND];
const SYSTEM_COMMANDS = [
...NODE_SYSTEM_RUN_COMMANDS,
...NODE_EXEC_APPROVALS_COMMANDS,
...NODE_FILE_COMMANDS,
NODE_SYSTEM_NOTIFY_COMMAND,
...NODE_BROWSER_PROXY_COMMANDS,
NODE_MCP_TOOLS_CALL_COMMAND,
NODE_AGENT_CLI_CLAUDE_RUN_COMMAND
];
const DESKTOP_HOST_COMMANDS = /* @__PURE__ */ new Set([
...NODE_SYSTEM_RUN_COMMANDS,
...NODE_EXEC_APPROVALS_COMMANDS,
...NODE_FILE_COMMANDS,
...NODE_BROWSER_PROXY_COMMANDS,
NODE_MCP_TOOLS_CALL_COMMAND,
NODE_AGENT_CLI_CLAUDE_RUN_COMMAND,
...SCREEN_COMMANDS,
NODE_DESKTOP_STREAM_COMMAND
]);
const UNKNOWN_PLATFORM_COMMANDS = [
...CAMERA_COMMANDS,
...MOBILE_NODE_COMMANDS.location,
NODE_SYSTEM_NOTIFY_COMMAND
];
const DEFAULT_DANGEROUS_NODE_COMMANDS = [
...CAMERA_DANGEROUS_COMMANDS,
...SCREEN_DANGEROUS_COMMANDS,
...CONTACTS_DANGEROUS_COMMANDS,
...CALENDAR_DANGEROUS_COMMANDS,
...REMINDERS_DANGEROUS_COMMANDS,
...SMS_DANGEROUS_COMMANDS,
...HEALTH_DANGEROUS_COMMANDS
];
const PLATFORM_DEFAULTS = {
ios: [
...CAMERA_COMMANDS,
...MOBILE_NODE_COMMANDS.location,
...MOBILE_NODE_COMMANDS.device,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
...IOS_SYSTEM_COMMANDS
],
watchos: [...MOBILE_NODE_COMMANDS.device, ...IOS_SYSTEM_COMMANDS],
android: [
...CAMERA_COMMANDS,
...MOBILE_NODE_COMMANDS.location,
...MOBILE_NODE_COMMANDS.androidNotification,
NODE_SYSTEM_NOTIFY_COMMAND,
...ANDROID_DEVICE_COMMANDS,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...CALL_LOG_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
...MOBILE_UI_COMMANDS
],
macos: [
...CAMERA_COMMANDS,
...MAC_CAMERA_COMMANDS,
...MOBILE_NODE_COMMANDS.location,
...MOBILE_NODE_COMMANDS.device,
NODE_DEVICE_APPS_COMMAND,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
...SYSTEM_COMMANDS,
...SCREEN_COMMANDS,
...COMPUTER_COMMANDS
],
linux: [
...SYSTEM_COMMANDS,
...SCREEN_COMMANDS,
...COMPUTER_COMMANDS
],
windows: [
...CAMERA_COMMANDS,
...MOBILE_NODE_COMMANDS.location,
...MOBILE_NODE_COMMANDS.device,
...SYSTEM_COMMANDS,
...SCREEN_COMMANDS,
...COMPUTER_COMMANDS
],
unknown: [...UNKNOWN_PLATFORM_COMMANDS]
};
const CANONICAL_PLATFORM_IDS = /* @__PURE__ */ new Set([
"ios",
"watchos",
"android",
"macos",
"windows",
"linux"
]);
const DEVICE_FAMILY_TOKEN_RULES = [
{
id: "ios",
tokens: [
"iphone",
"ipad",
"ios"
]
},
{
id: "watchos",
tokens: ["apple watch", "watchos"]
},
{
id: "android",
tokens: ["android"]
},
{
id: "macos",
tokens: ["mac"]
},
{
id: "windows",
tokens: ["windows"]
},
{
id: "linux",
tokens: ["linux"]
}
];
function resolvePlatformIdByExactMatch(value) {
if (CANONICAL_PLATFORM_IDS.has(value)) return value;
}
function platformMatchesDeviceFamily(platformId, family) {
switch (platformId) {
case "ios": return family === "" || /^(?:iphone|ipad|ios)$/.test(family);
case "watchos": return family === "apple watch" || family === "watchos";
case "android": return family === "" || family === "android";
case "macos": return family === "mac";
case "windows": return family === "windows";
case "linux": return family === "linux";
}
return false;
}
function resolvePlatformIdByNativeLabel(platform, deviceFamily) {
if (/^(?:ios|ipados) \d+(?:\.\d+){0,2}$/.test(platform)) return /^(?:iphone|ipad|ios)$/.test(deviceFamily) ? "ios" : void 0;
if (/^watchos \d+(?:\.\d+){0,2}$/.test(platform)) return /^(?:apple watch|watchos)$/.test(deviceFamily) ? "watchos" : void 0;
if (/^macos \d+(?:\.\d+){0,2}$/.test(platform)) return deviceFamily === "mac" ? "macos" : void 0;
if (/^android \d+(?: \(sdk \d+\))?$/.test(platform)) return deviceFamily === "android" ? "android" : void 0;
}
function resolvePlatformIdByDeviceFamily(value) {
for (const rule of DEVICE_FAMILY_TOKEN_RULES) if (rule.tokens.some((token) => value.includes(token))) return rule.id;
}
function normalizePlatformId(platform, deviceFamily) {
const raw = normalizeDeviceMetadataForPolicy(platform);
const family = normalizeDeviceMetadataForPolicy(deviceFamily);
const byPlatform = resolvePlatformIdByExactMatch(raw);
if (byPlatform) return platformMatchesDeviceFamily(byPlatform, family) ? byPlatform : "unknown";
const byNativeLabel = resolvePlatformIdByNativeLabel(raw, family);
if (byNativeLabel) return byNativeLabel;
if (raw) return "unknown";
return resolvePlatformIdByDeviceFamily(family) ?? "unknown";
}
function listDangerousPluginNodeCommands() {
const registry = getActivePluginGatewayNodePolicyRegistry();
if (!registry) return [];
const commands = [];
registry.nodeHostCommands.forEach(({ command }) => {
if (command.dangerous === true) commands.push(command.command);
});
registry.nodeInvokePolicies.forEach(({ policy }) => {
if (policy.dangerous === true) policy.commands.forEach((command) => commands.push(command));
});
return normalizeUniqueStringEntries(commands);
}
function listDefaultPluginNodeCommands(platformId) {
if (platformId === "watchos") return [];
const registry = getActivePluginGatewayNodePolicyRegistry();
if (!registry) return [];
const commands = [];
registry.nodeInvokePolicies.forEach(({ policy }) => {
if (policy.dangerous !== true && policy.defaultPlatforms?.includes(platformId)) policy.commands.forEach((command) => commands.push(command));
});
registry.nodeHostCommands.forEach(({ command: { dangerous, agentTool, command } }) => {
if (dangerous !== true && agentTool?.defaultPlatforms?.includes(platformId)) commands.push(command);
});
return normalizeUniqueStringEntries(commands);
}
function isForegroundRestrictedPluginNodeCommand(command) {
const registry = getActivePluginGatewayNodePolicyRegistry();
if (!registry) return false;
const normalized = command.trim();
if (!normalized) return false;
return registry.nodeInvokePolicies.some((entry) => entry.policy.foregroundRestrictedOnIos === true && entry.policy.commands.some((policyCommand) => policyCommand.trim() === normalized));
}
function isDesktopPlatformId(platformId) {
return platformId === "macos" || platformId === "windows" || platformId === "linux";
}
function filterDesktopHostCommandDefaults(params) {
if (params.includeDesktopHostCommands === true || !isDesktopPlatformId(params.platformId)) return [...params.commands];
return params.commands.filter((command) => !DESKTOP_HOST_COMMANDS.has(command));
}
function filterApprovedRuntimeCommands(params) {
if (!isDesktopPlatformId(params.platformId)) return [];
return params.commands.filter((command) => DESKTOP_HOST_COMMANDS.has(command.trim()));
}
function isLiveNodeSession(node) {
return typeof node?.nodeId === "string" && node.nodeId.trim() !== "" && typeof node.connId === "string" && node.connId.trim() !== "";
}
function hasTalkSurface(node) {
if (!node) return false;
return (node.caps ?? []).some((capability) => normalizeOptionalLowercaseString(capability) === "talk") || (node.commands ?? []).some((command) => normalizeOptionalLowercaseString(command)?.startsWith("talk."));
}
function resolveNodeCommandAllowlistInternal(cfg, node, options) {
const platformId = normalizePlatformId(node?.platform, node?.deviceFamily);
const base = filterDesktopHostCommandDefaults({
platformId,
commands: expectDefined(PLATFORM_DEFAULTS[platformId], "platform defaults entry at platform id") ?? PLATFORM_DEFAULTS.unknown,
includeDesktopHostCommands: options?.includeDesktopHostCommands
});
const watchRelayCommands = platformId === "ios" && normalizeDeviceMetadataForPolicy(node?.deviceFamily) === "iphone" ? IOS_WATCH_RELAY_COMMANDS : [];
const talkCommands = hasTalkSurface(node) ? TALK_PTT_COMMANDS : [];
const pluginDefaults = listDefaultPluginNodeCommands(platformId);
const approved = filterApprovedRuntimeCommands({
platformId,
commands: node?.approvedCommands ?? (isLiveNodeSession(node) ? node?.commands ?? [] : [])
});
const extra = cfg.gateway?.nodes?.commands?.allow ?? [];
const deny = new Set(cfg.gateway?.nodes?.commands?.deny ?? []);
const baseCommands = new Set(base);
const dangerousPluginCommands = new Set(listDangerousPluginNodeCommands().filter((command) => !baseCommands.has(command)));
const dangerousBuiltinCommands = options?.includeDangerousDefaults === true ? /* @__PURE__ */ new Set() : new Set(DEFAULT_DANGEROUS_NODE_COMMANDS);
const allow = new Set([
...base,
...watchRelayCommands,
...talkCommands,
...pluginDefaults,
...approved,
...extra
].map((cmd) => cmd.trim()).filter((cmd) => cmd && !dangerousPluginCommands.has(cmd) && !dangerousBuiltinCommands.has(cmd)));
for (const cmd of extra) {
const trimmed = cmd.trim();
if (trimmed) allow.add(trimmed);
}
if (cfg.wizard?.appRecommendations === false) allow.delete(NODE_DEVICE_APPS_COMMAND);
const denyExemptDeclarable = options?.includeDangerousDefaults === true ? new Set(DEFAULT_DANGEROUS_NODE_COMMANDS) : /* @__PURE__ */ new Set();
for (const blocked of deny) {
const trimmed = blocked.trim();
if (trimmed && !denyExemptDeclarable.has(trimmed)) allow.delete(trimmed);
}
for (const privateCommand of NODE_WORKER_PRIVATE_COMMANDS) allow.delete(privateCommand);
return allow;
}
function resolveNodeCommandAllowlist(cfg, node) {
return resolveNodeCommandAllowlistInternal(cfg, node);
}
function resolveNodePairingCommandAllowlist(cfg, node) {
return resolveNodeCommandAllowlistInternal(cfg, node, {
includeDesktopHostCommands: true,
includeDangerousDefaults: true
});
}
function normalizeDeclaredCommands(commands) {
if (!Array.isArray(commands)) return [];
const seen = /* @__PURE__ */ new Set();
const normalized = [];
for (const value of commands) {
const trimmed = value.trim();
if (!trimmed || seen.has(trimmed) || isPrivateNodeInvokeCommand(trimmed)) continue;
seen.add(trimmed);
normalized.push(trimmed);
}
return normalized;
}
function normalizeDeclaredNodeCommands(params) {
return normalizeDeclaredCommands(params.declaredCommands).filter((command) => params.allowlist.has(command));
}
const CAPABILITY_COMMAND_FAMILIES = /* @__PURE__ */ new Map([
["camera", /* @__PURE__ */ new Set([
...CAMERA_COMMANDS,
...MAC_CAMERA_COMMANDS,
...CAMERA_DANGEROUS_COMMANDS
])],
["computer", new Set(COMPUTER_COMMANDS)],
["location", new Set(MOBILE_NODE_COMMANDS.location)],
["screen", /* @__PURE__ */ new Set([...SCREEN_COMMANDS, ...SCREEN_DANGEROUS_COMMANDS])]
]);
/** Drops capabilities whose commands policy withheld without admitting a sibling. */
function retainFulfilledNodeCapabilities(params) {
return params.caps.filter((capability) => {
const family = CAPABILITY_COMMAND_FAMILIES.get(capability);
return !family || !params.withheldCommands.some((command) => family.has(command)) || params.admittedCommands.some((command) => family.has(command));
});
}
function isNodeCommandAllowed(params) {
const command = params.command.trim();
if (!command) return {
ok: false,
reason: "command required"
};
if (isPrivateNodeInvokeCommand(command)) return {
ok: false,
reason: "command not allowlisted"
};
if (!params.allowlist.has(command)) return {
ok: false,
reason: "command not allowlisted"
};
if (Array.isArray(params.declaredCommands) && params.declaredCommands.length > 0) {
if (!params.declaredCommands.includes(command)) return {
ok: false,
reason: "command not declared by node"
};
} else return {
ok: false,
reason: "node did not declare commands"
};
return { ok: true };
}
/**
* Resolves declaration, pairing, and runtime policy once at their Gateway owner.
* Clients receive one closed state instead of rebuilding authority from partial lists.
*/
function resolveRequiredNodeCommandAuthority(params) {
const declaredCommands = new Set(params.declaredCommands);
const effectiveCommands = new Set(params.effectiveCommands);
const denied = params.requiredCommands.find((cmd) => params.withheldCommands.includes(cmd));
if (denied) return {
command: denied,
state: "unauthorized"
};
for (const command of params.requiredCommands) {
if (effectiveCommands.has(command) && isNodeCommandAllowed({
command,
declaredCommands: params.effectiveCommands,
allowlist: params.allowlist
}).ok) continue;
if (declaredCommands.has(command) && !effectiveCommands.has(command)) return {
command,
state: "pending-approval"
};
if (declaredCommands.has(command)) return {
command,
state: "unauthorized"
};
return {
command,
state: "undeclared"
};
}
const command = params.requiredCommands[0];
return command ? {
command,
state: "invocable"
} : void 0;
}
//#endregion
export { isForegroundRestrictedPluginNodeCommand as a, normalizeDeclaredNodeCommands as c, resolveRequiredNodeCommandAuthority as d, retainFulfilledNodeCapabilities as f, TALK_PTT_COMMANDS as i, resolveNodeCommandAllowlist as l, IOS_WATCH_RELAY_COMMANDS as n, isNodeCommandAllowed as o, PLATFORM_DEFAULTS as r, listDangerousPluginNodeCommands as s, DEFAULT_DANGEROUS_NODE_COMMANDS as t, resolveNodePairingCommandAllowlist as u };