mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-17 04:50:51 +00:00
* Tests: add fresh module import helper * Process: share command queue runtime state * Agents: share embedded run runtime state * Reply: share followup queue runtime state * Reply: share followup drain callback state * Reply: share queued message dedupe state * Reply: share inbound dedupe state * Tests: cover shared command queue runtime state * Tests: cover shared embedded run runtime state * Tests: cover shared followup queue runtime state * Tests: cover shared inbound dedupe state * Tests: cover shared Slack thread participation state * Slack: share sent thread participation state * Tests: document fresh import helper * Telegram: share draft stream runtime state * Tests: cover shared Telegram draft stream state * Telegram: share sent message cache state * Tests: cover shared Telegram sent message cache * Telegram: share thread binding runtime state * Tests: cover shared Telegram thread binding state * Tests: avoid duplicate shared queue reset * refactor(runtime): centralize global singleton access * refactor(runtime): preserve undefined global singleton values * test(runtime): cover undefined global singleton values --------- Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
import { resolveGlobalMap } from "../shared/global-singleton.js";
|
|
|
|
/**
|
|
* In-memory cache of Slack threads the bot has participated in.
|
|
* Used to auto-respond in threads without requiring @mention after the first reply.
|
|
* Follows a similar TTL pattern to the MS Teams and Telegram sent-message caches.
|
|
*/
|
|
|
|
const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
const MAX_ENTRIES = 5000;
|
|
|
|
/**
|
|
* Keep Slack thread participation shared across bundled chunks so thread
|
|
* auto-reply gating does not diverge between prepare/dispatch call paths.
|
|
*/
|
|
const SLACK_THREAD_PARTICIPATION_KEY = Symbol.for("openclaw.slackThreadParticipation");
|
|
|
|
const threadParticipation = resolveGlobalMap<string, number>(SLACK_THREAD_PARTICIPATION_KEY);
|
|
|
|
function makeKey(accountId: string, channelId: string, threadTs: string): string {
|
|
return `${accountId}:${channelId}:${threadTs}`;
|
|
}
|
|
|
|
function evictExpired(): void {
|
|
const now = Date.now();
|
|
for (const [key, timestamp] of threadParticipation) {
|
|
if (now - timestamp > TTL_MS) {
|
|
threadParticipation.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
function evictOldest(): void {
|
|
const oldest = threadParticipation.keys().next().value;
|
|
if (oldest) {
|
|
threadParticipation.delete(oldest);
|
|
}
|
|
}
|
|
|
|
export function recordSlackThreadParticipation(
|
|
accountId: string,
|
|
channelId: string,
|
|
threadTs: string,
|
|
): void {
|
|
if (!accountId || !channelId || !threadTs) {
|
|
return;
|
|
}
|
|
if (threadParticipation.size >= MAX_ENTRIES) {
|
|
evictExpired();
|
|
}
|
|
if (threadParticipation.size >= MAX_ENTRIES) {
|
|
evictOldest();
|
|
}
|
|
threadParticipation.set(makeKey(accountId, channelId, threadTs), Date.now());
|
|
}
|
|
|
|
export function hasSlackThreadParticipation(
|
|
accountId: string,
|
|
channelId: string,
|
|
threadTs: string,
|
|
): boolean {
|
|
if (!accountId || !channelId || !threadTs) {
|
|
return false;
|
|
}
|
|
const key = makeKey(accountId, channelId, threadTs);
|
|
const timestamp = threadParticipation.get(key);
|
|
if (timestamp == null) {
|
|
return false;
|
|
}
|
|
if (Date.now() - timestamp > TTL_MS) {
|
|
threadParticipation.delete(key);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function clearSlackThreadParticipationCache(): void {
|
|
threadParticipation.clear();
|
|
}
|