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

View File

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

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(); 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: { export async function runCronIsolatedAgentTurn(params: {
cfg: OpenClawConfig; cfg: OpenClawConfig;
deps: CliDeps; deps: CliDeps;

View File

@@ -209,7 +209,11 @@ export {
} from "../channels/plugins/config-schema.js"; } from "../channels/plugins/config-schema.js";
export type { ChannelDock } from "../channels/dock.js"; export type { ChannelDock } from "../channels/dock.js";
export { getChatChannelMeta } from "../channels/registry.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 { export type {
BlockStreamingCoalesceConfig, BlockStreamingCoalesceConfig,
DmPolicy, DmPolicy,

View File

@@ -9,7 +9,11 @@ export {
readStringParam, readStringParam,
} from "../agents/tools/common.js"; } from "../agents/tools/common.js";
export type { ReplyPayload } from "../auto-reply/types.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 { mergeAllowlist, summarizeMapping } from "../channels/allowlists/resolve-utils.js";
export { resolveControlCommandGate } from "../channels/command-gating.js"; export { resolveControlCommandGate } from "../channels/command-gating.js";
export type { NormalizedLocation } from "../channels/location.js"; export type { NormalizedLocation } from "../channels/location.js";

View File

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