perf(slack): memoize allow-from and mention paths

This commit is contained in:
Peter Steinberger
2026-03-02 20:18:41 +00:00
parent 2f352306fe
commit fb5d8a9cd1
4 changed files with 166 additions and 11 deletions

View File

@@ -8,13 +8,89 @@ import {
import { resolveSlackChannelConfig } from "./channel-config.js";
import { normalizeSlackChannelType, type SlackMonitorContext } from "./context.js";
type ResolvedAllowFromLists = {
allowFrom: string[];
allowFromLower: string[];
};
type SlackAllowFromCacheState = {
baseSignature?: string;
base?: ResolvedAllowFromLists;
pairingKey?: string;
pairing?: ResolvedAllowFromLists;
pairingExpiresAtMs?: number;
pairingPending?: Promise<ResolvedAllowFromLists>;
};
let slackAllowFromCache = new WeakMap<SlackMonitorContext, SlackAllowFromCacheState>();
const DEFAULT_PAIRING_ALLOW_FROM_CACHE_TTL_MS = 5000;
function getPairingAllowFromCacheTtlMs(): number {
const raw = process.env.OPENCLAW_SLACK_PAIRING_ALLOWFROM_CACHE_TTL_MS?.trim();
if (!raw) {
return DEFAULT_PAIRING_ALLOW_FROM_CACHE_TTL_MS;
}
const parsed = Number(raw);
if (!Number.isFinite(parsed)) {
return DEFAULT_PAIRING_ALLOW_FROM_CACHE_TTL_MS;
}
return Math.max(0, Math.floor(parsed));
}
function getAllowFromCacheState(ctx: SlackMonitorContext): SlackAllowFromCacheState {
const existing = slackAllowFromCache.get(ctx);
if (existing) {
return existing;
}
const next: SlackAllowFromCacheState = {};
slackAllowFromCache.set(ctx, next);
return next;
}
function buildBaseAllowFrom(ctx: SlackMonitorContext): ResolvedAllowFromLists {
const allowFrom = normalizeAllowList(ctx.allowFrom);
return {
allowFrom,
allowFromLower: normalizeAllowListLower(allowFrom),
};
}
export async function resolveSlackEffectiveAllowFrom(
ctx: SlackMonitorContext,
options?: { includePairingStore?: boolean },
) {
const includePairingStore = options?.includePairingStore === true;
let storeAllowFrom: string[] = [];
if (includePairingStore) {
const cache = getAllowFromCacheState(ctx);
const baseSignature = JSON.stringify(ctx.allowFrom);
if (cache.baseSignature !== baseSignature || !cache.base) {
cache.baseSignature = baseSignature;
cache.base = buildBaseAllowFrom(ctx);
cache.pairing = undefined;
cache.pairingKey = undefined;
cache.pairingExpiresAtMs = undefined;
cache.pairingPending = undefined;
}
if (!includePairingStore) {
return cache.base;
}
const ttlMs = getPairingAllowFromCacheTtlMs();
const nowMs = Date.now();
const pairingKey = `${ctx.accountId}:${ctx.dmPolicy}`;
if (
ttlMs > 0 &&
cache.pairing &&
cache.pairingKey === pairingKey &&
(cache.pairingExpiresAtMs ?? 0) >= nowMs
) {
return cache.pairing;
}
if (cache.pairingPending && cache.pairingKey === pairingKey) {
return await cache.pairingPending;
}
const pairingPending = (async (): Promise<ResolvedAllowFromLists> => {
let storeAllowFrom: string[] = [];
try {
const resolved = await readStoreAllowFromForDmPolicy({
provider: "slack",
@@ -25,10 +101,34 @@ export async function resolveSlackEffectiveAllowFrom(
} catch {
storeAllowFrom = [];
}
const allowFrom = normalizeAllowList([...(cache.base?.allowFrom ?? []), ...storeAllowFrom]);
return {
allowFrom,
allowFromLower: normalizeAllowListLower(allowFrom),
};
})();
cache.pairingKey = pairingKey;
cache.pairingPending = pairingPending;
try {
const resolved = await pairingPending;
if (ttlMs > 0) {
cache.pairing = resolved;
cache.pairingExpiresAtMs = nowMs + ttlMs;
} else {
cache.pairing = undefined;
cache.pairingExpiresAtMs = undefined;
}
return resolved;
} finally {
if (cache.pairingPending === pairingPending) {
cache.pairingPending = undefined;
}
}
const allowFrom = normalizeAllowList([...ctx.allowFrom, ...storeAllowFrom]);
const allowFromLower = normalizeAllowListLower(allowFrom);
return { allowFrom, allowFromLower };
}
export function clearSlackAllowFromCacheForTest(): void {
slackAllowFromCache = new WeakMap<SlackMonitorContext, SlackAllowFromCacheState>();
}
export function isSlackSenderAllowListed(params: {