refactor: compile allowlist matchers

This commit is contained in:
Peter Steinberger
2026-03-11 00:07:16 +00:00
parent fa0329c340
commit f4a4b50cd5
6 changed files with 85 additions and 48 deletions

View File

@@ -1,6 +1,7 @@
import {
compileAllowlist,
normalizeStringEntries,
resolveAllowlistMatchByCandidates,
resolveAllowlistCandidates,
type AllowlistMatch,
} from "openclaw/plugin-sdk/matrix";
@@ -75,11 +76,11 @@ export function resolveMatrixAllowListMatch(params: {
allowList: string[];
userId?: string;
}): MatrixAllowListMatch {
const allowList = params.allowList;
if (allowList.length === 0) {
const compiledAllowList = compileAllowlist(params.allowList);
if (compiledAllowList.set.size === 0) {
return { allowed: false };
}
if (allowList.includes("*")) {
if (compiledAllowList.wildcard) {
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
}
const userId = normalizeMatrixUser(params.userId);
@@ -88,7 +89,10 @@ export function resolveMatrixAllowListMatch(params: {
{ value: userId ? `matrix:${userId}` : "", source: "prefixed-id" },
{ value: userId ? `user:${userId}` : "", source: "prefixed-user" },
];
return resolveAllowlistMatchByCandidates({ allowList, candidates });
return resolveAllowlistCandidates({
compiledAllowlist: compiledAllowList,
candidates,
});
}
export function resolveMatrixAllowListMatches(params: { allowList: string[]; userId?: string }) {

View File

@@ -16,22 +16,40 @@ export type AllowlistMatch<TSource extends string = AllowlistMatchSource> = {
matchSource?: TSource;
};
export type CompiledAllowlist = {
set: ReadonlySet<string>;
wildcard: boolean;
};
export function formatAllowlistMatchMeta(
match?: { matchKey?: string; matchSource?: string } | null,
): string {
return `matchKey=${match?.matchKey ?? "none"} matchSource=${match?.matchSource ?? "none"}`;
}
export function resolveAllowlistMatchByCandidates<TSource extends string>(params: {
allowList: string[];
export function compileAllowlist(entries: ReadonlyArray<string>): CompiledAllowlist {
const set = new Set(entries.filter(Boolean));
return {
set,
wildcard: set.has("*"),
};
}
function compileSimpleAllowlist(entries: ReadonlyArray<string | number>): CompiledAllowlist {
return compileAllowlist(
entries.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean),
);
}
export function resolveAllowlistCandidates<TSource extends string>(params: {
compiledAllowlist: CompiledAllowlist;
candidates: Array<{ value?: string; source: TSource }>;
}): AllowlistMatch<TSource> {
const allowSet = resolveAllowListSet(params.allowList);
for (const candidate of params.candidates) {
if (!candidate.value) {
continue;
}
if (allowSet.has(candidate.value)) {
if (params.compiledAllowlist.set.has(candidate.value)) {
return {
allowed: true,
matchKey: candidate.value,
@@ -42,15 +60,25 @@ export function resolveAllowlistMatchByCandidates<TSource extends string>(params
return { allowed: false };
}
export function resolveAllowlistMatchByCandidates<TSource extends string>(params: {
allowList: ReadonlyArray<string>;
candidates: Array<{ value?: string; source: TSource }>;
}): AllowlistMatch<TSource> {
return resolveAllowlistCandidates({
compiledAllowlist: compileAllowlist(params.allowList),
candidates: params.candidates,
});
}
export function resolveAllowlistMatchSimple(params: {
allowFrom: Array<string | number>;
allowFrom: ReadonlyArray<string | number>;
senderId: string;
senderName?: string | null;
allowNameMatching?: boolean;
}): AllowlistMatch<"wildcard" | "id" | "name"> {
const allowFrom = resolveSimpleAllowFrom(params.allowFrom);
const allowFrom = compileSimpleAllowlist(params.allowFrom);
if (allowFrom.size === 0) {
if (allowFrom.set.size === 0) {
return { allowed: false };
}
if (allowFrom.wildcard) {
@@ -58,34 +86,17 @@ export function resolveAllowlistMatchSimple(params: {
}
const senderId = params.senderId.toLowerCase();
if (allowFrom.set.has(senderId)) {
return { allowed: true, matchKey: senderId, matchSource: "id" };
}
const senderName = params.senderName?.toLowerCase();
if (params.allowNameMatching === true && senderName && allowFrom.set.has(senderName)) {
return { allowed: true, matchKey: senderName, matchSource: "name" };
}
return { allowed: false };
}
function resolveAllowListSet(allowList: string[]): Set<string> {
return new Set(allowList);
}
function resolveSimpleAllowFrom(allowFrom: Array<string | number>): {
normalized: string[];
size: number;
wildcard: boolean;
set: Set<string>;
} {
const normalized = allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean);
const set = new Set(normalized);
return {
normalized,
size: allowFrom.length,
wildcard: set.has("*"),
set,
};
return resolveAllowlistCandidates({
compiledAllowlist: allowFrom,
candidates: [
{ value: senderId, source: "id" },
...(params.allowNameMatching === true && senderName
? ([{ value: senderName, source: "name" as const }] satisfies Array<{
value?: string;
source: "id" | "name";
}>)
: []),
],
});
}

View File

@@ -198,6 +198,16 @@ function appendCronDeliveryInstruction(params: {
return `${params.commandBody}\n\nReturn your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim();
}
function resolveCronEmbeddedAgentLane(lane?: string) {
const trimmed = lane?.trim();
// Cron jobs already execute inside the cron command lane. Reusing that same
// lane for the nested embedded-agent run deadlocks: the outer cron task holds
// the lane while the inner run waits to reacquire it.
if (!trimmed || trimmed === "cron") {
return CommandLane.Nested;
}
return trimmed;
}
export async function runCronIsolatedAgentTurn(params: {
cfg: OpenClawConfig;
deps: CliDeps;

View File

@@ -209,7 +209,11 @@ export {
} from "../channels/plugins/config-schema.js";
export type { ChannelDock } from "../channels/dock.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { resolveAllowlistMatchByCandidates } from "../channels/allowlist-match.js";
export {
compileAllowlist,
resolveAllowlistCandidates,
resolveAllowlistMatchByCandidates,
} from "../channels/allowlist-match.js";
export type {
BlockStreamingCoalesceConfig,
DmPolicy,

View File

@@ -9,7 +9,11 @@ export {
readStringParam,
} from "../agents/tools/common.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { resolveAllowlistMatchByCandidates } from "../channels/allowlist-match.js";
export {
compileAllowlist,
resolveAllowlistCandidates,
resolveAllowlistMatchByCandidates,
} from "../channels/allowlist-match.js";
export { mergeAllowlist, summarizeMapping } from "../channels/allowlists/resolve-utils.js";
export { resolveControlCommandGate } from "../channels/command-gating.js";
export type { NormalizedLocation } from "../channels/location.js";

View File

@@ -1,5 +1,6 @@
import {
resolveAllowlistMatchByCandidates,
compileAllowlist,
resolveAllowlistCandidates,
type AllowlistMatch,
} from "../../channels/allowlist-match.js";
import {
@@ -56,11 +57,11 @@ export function resolveSlackAllowListMatch(params: {
name?: string;
allowNameMatching?: boolean;
}): SlackAllowListMatch {
const allowList = params.allowList;
if (allowList.length === 0) {
const compiledAllowList = compileAllowlist(params.allowList);
if (compiledAllowList.set.size === 0) {
return { allowed: false };
}
if (allowList.includes("*")) {
if (compiledAllowList.wildcard) {
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
}
const id = params.id?.toLowerCase();
@@ -78,7 +79,10 @@ export function resolveSlackAllowListMatch(params: {
] satisfies Array<{ value?: string; source: SlackAllowListSource }>)
: []),
];
return resolveAllowlistMatchByCandidates({ allowList, candidates });
return resolveAllowlistCandidates({
compiledAllowlist: compiledAllowList,
candidates,
});
}
export function allowListMatches(params: {