diff --git a/extensions/matrix/src/matrix/monitor/allowlist.ts b/extensions/matrix/src/matrix/monitor/allowlist.ts index e9402c38362..326360cade5 100644 --- a/extensions/matrix/src/matrix/monitor/allowlist.ts +++ b/extensions/matrix/src/matrix/monitor/allowlist.ts @@ -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 }) { diff --git a/src/channels/allowlist-match.ts b/src/channels/allowlist-match.ts index 60a6bd88baa..f32d5a2487c 100644 --- a/src/channels/allowlist-match.ts +++ b/src/channels/allowlist-match.ts @@ -16,22 +16,40 @@ export type AllowlistMatch = { matchSource?: TSource; }; +export type CompiledAllowlist = { + set: ReadonlySet; + wildcard: boolean; +}; + export function formatAllowlistMatchMeta( match?: { matchKey?: string; matchSource?: string } | null, ): string { return `matchKey=${match?.matchKey ?? "none"} matchSource=${match?.matchSource ?? "none"}`; } -export function resolveAllowlistMatchByCandidates(params: { - allowList: string[]; +export function compileAllowlist(entries: ReadonlyArray): CompiledAllowlist { + const set = new Set(entries.filter(Boolean)); + return { + set, + wildcard: set.has("*"), + }; +} + +function compileSimpleAllowlist(entries: ReadonlyArray): CompiledAllowlist { + return compileAllowlist( + entries.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean), + ); +} + +export function resolveAllowlistCandidates(params: { + compiledAllowlist: CompiledAllowlist; candidates: Array<{ value?: string; source: TSource }>; }): AllowlistMatch { - 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(params return { allowed: false }; } +export function resolveAllowlistMatchByCandidates(params: { + allowList: ReadonlyArray; + candidates: Array<{ value?: string; source: TSource }>; +}): AllowlistMatch { + return resolveAllowlistCandidates({ + compiledAllowlist: compileAllowlist(params.allowList), + candidates: params.candidates, + }); +} + export function resolveAllowlistMatchSimple(params: { - allowFrom: Array; + allowFrom: ReadonlyArray; 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 { - return new Set(allowList); -} - -function resolveSimpleAllowFrom(allowFrom: Array): { - normalized: string[]; - size: number; - wildcard: boolean; - set: Set; -} { - 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"; + }>) + : []), + ], + }); } diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 4c7a5c87fe2..4db6b88b57f 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -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; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index d16f077c13f..2aaafca8ccb 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -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, diff --git a/src/plugin-sdk/matrix.ts b/src/plugin-sdk/matrix.ts index c1c29a776a1..577a690a4cb 100644 --- a/src/plugin-sdk/matrix.ts +++ b/src/plugin-sdk/matrix.ts @@ -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"; diff --git a/src/slack/monitor/allow-list.ts b/src/slack/monitor/allow-list.ts index bc552c02cf4..53fc6c58505 100644 --- a/src/slack/monitor/allow-list.ts +++ b/src/slack/monitor/allow-list.ts @@ -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: {