Files
openclaw/src/auto-reply/reply/reply-elevated.ts
2026-02-14 15:39:45 +00:00

205 lines
5.6 KiB
TypeScript

import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js";
import type { MsgContext } from "../templating.js";
import { resolveAgentConfig } from "../../agents/agent-scope.js";
import { getChannelDock } from "../../channels/dock.js";
import { normalizeChannelId } from "../../channels/plugins/index.js";
import { CHAT_CHANNEL_ORDER } from "../../channels/registry.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
export { formatElevatedUnavailableMessage } from "./elevated-unavailable.js";
function normalizeAllowToken(value?: string) {
if (!value) {
return "";
}
return value.trim().toLowerCase();
}
function slugAllowToken(value?: string) {
if (!value) {
return "";
}
let text = value.trim().toLowerCase();
if (!text) {
return "";
}
text = text.replace(/^[@#]+/, "");
text = text.replace(/[\s_]+/g, "-");
text = text.replace(/[^a-z0-9-]+/g, "-");
return text.replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
}
const SENDER_PREFIXES = [
...CHAT_CHANNEL_ORDER,
INTERNAL_MESSAGE_CHANNEL,
"user",
"group",
"channel",
];
const SENDER_PREFIX_RE = new RegExp(`^(${SENDER_PREFIXES.join("|")}):`, "i");
function stripSenderPrefix(value?: string) {
if (!value) {
return "";
}
const trimmed = value.trim();
return trimmed.replace(SENDER_PREFIX_RE, "");
}
function resolveElevatedAllowList(
allowFrom: AgentElevatedAllowFromConfig | undefined,
provider: string,
fallbackAllowFrom?: Array<string | number>,
): Array<string | number> | undefined {
if (!allowFrom) {
return fallbackAllowFrom;
}
const value = allowFrom[provider];
return Array.isArray(value) ? value : fallbackAllowFrom;
}
function isApprovedElevatedSender(params: {
provider: string;
ctx: MsgContext;
allowFrom?: AgentElevatedAllowFromConfig;
fallbackAllowFrom?: Array<string | number>;
}): boolean {
const rawAllow = resolveElevatedAllowList(
params.allowFrom,
params.provider,
params.fallbackAllowFrom,
);
if (!rawAllow || rawAllow.length === 0) {
return false;
}
const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean);
if (allowTokens.length === 0) {
return false;
}
if (allowTokens.some((entry) => entry === "*")) {
return true;
}
const tokens = new Set<string>();
const addToken = (value?: string) => {
if (!value) {
return;
}
const trimmed = value.trim();
if (!trimmed) {
return;
}
tokens.add(trimmed);
const normalized = normalizeAllowToken(trimmed);
if (normalized) {
tokens.add(normalized);
}
const slugged = slugAllowToken(trimmed);
if (slugged) {
tokens.add(slugged);
}
};
addToken(params.ctx.SenderName);
addToken(params.ctx.SenderUsername);
addToken(params.ctx.SenderTag);
addToken(params.ctx.SenderE164);
addToken(params.ctx.From);
addToken(stripSenderPrefix(params.ctx.From));
addToken(params.ctx.To);
addToken(stripSenderPrefix(params.ctx.To));
for (const rawEntry of allowTokens) {
const entry = rawEntry.trim();
if (!entry) {
continue;
}
const stripped = stripSenderPrefix(entry);
if (tokens.has(entry) || tokens.has(stripped)) {
return true;
}
const normalized = normalizeAllowToken(stripped);
if (normalized && tokens.has(normalized)) {
return true;
}
const slugged = slugAllowToken(stripped);
if (slugged && tokens.has(slugged)) {
return true;
}
}
return false;
}
export function resolveElevatedPermissions(params: {
cfg: OpenClawConfig;
agentId: string;
ctx: MsgContext;
provider: string;
}): {
enabled: boolean;
allowed: boolean;
failures: Array<{ gate: string; key: string }>;
} {
const globalConfig = params.cfg.tools?.elevated;
const agentConfig = resolveAgentConfig(params.cfg, params.agentId)?.tools?.elevated;
const globalEnabled = globalConfig?.enabled !== false;
const agentEnabled = agentConfig?.enabled !== false;
const enabled = globalEnabled && agentEnabled;
const failures: Array<{ gate: string; key: string }> = [];
if (!globalEnabled) {
failures.push({ gate: "enabled", key: "tools.elevated.enabled" });
}
if (!agentEnabled) {
failures.push({
gate: "enabled",
key: "agents.list[].tools.elevated.enabled",
});
}
if (!enabled) {
return { enabled, allowed: false, failures };
}
if (!params.provider) {
failures.push({ gate: "provider", key: "ctx.Provider" });
return { enabled, allowed: false, failures };
}
const normalizedProvider = normalizeChannelId(params.provider);
const dockFallbackAllowFrom = normalizedProvider
? getChannelDock(normalizedProvider)?.elevated?.allowFromFallback?.({
cfg: params.cfg,
accountId: params.ctx.AccountId,
})
: undefined;
const fallbackAllowFrom = dockFallbackAllowFrom;
const globalAllowed = isApprovedElevatedSender({
provider: params.provider,
ctx: params.ctx,
allowFrom: globalConfig?.allowFrom,
fallbackAllowFrom,
});
if (!globalAllowed) {
failures.push({
gate: "allowFrom",
key: `tools.elevated.allowFrom.${params.provider}`,
});
return { enabled, allowed: false, failures };
}
const agentAllowed = agentConfig?.allowFrom
? isApprovedElevatedSender({
provider: params.provider,
ctx: params.ctx,
allowFrom: agentConfig.allowFrom,
fallbackAllowFrom,
})
: true;
if (!agentAllowed) {
failures.push({
gate: "allowFrom",
key: `agents.list[].tools.elevated.allowFrom.${params.provider}`,
});
}
return { enabled, allowed: globalAllowed && agentAllowed, failures };
}