


















@@ -1,4 +1,5 @@
11import { resolveGlobalDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
2+import { getOptionalSlackRuntime } from "./runtime.js";
2334/**
45 * In-memory cache of Slack threads the bot has participated in.
@@ -8,6 +9,22 @@ import { resolveGlobalDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
89910const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
1011const MAX_ENTRIES = 5000;
12+const PERSISTENT_MAX_ENTRIES = 1000;
13+const PERSISTENT_NAMESPACE = "slack.thread-participation";
14+15+type SlackThreadParticipationRecord = {
16+agentId?: string;
17+repliedAt: number;
18+};
19+20+type SlackThreadParticipationStore = {
21+register(
22+key: string,
23+value: SlackThreadParticipationRecord,
24+opts?: { ttlMs?: number },
25+): Promise<void>;
26+lookup(key: string): Promise<SlackThreadParticipationRecord | undefined>;
27+};
11281229/**
1330 * Keep Slack thread participation shared across bundled chunks so thread
@@ -19,19 +36,92 @@ const threadParticipation = resolveGlobalDedupeCache(SLACK_THREAD_PARTICIPATION_
1936maxSize: MAX_ENTRIES,
2037});
213839+let persistentStore: SlackThreadParticipationStore | undefined;
40+let persistentStoreDisabled = false;
41+2242function makeKey(accountId: string, channelId: string, threadTs: string): string {
2343return `${accountId}:${channelId}:${threadTs}`;
2444}
254546+function reportPersistentThreadParticipationError(error: unknown): void {
47+try {
48+getOptionalSlackRuntime()
49+?.logging.getChildLogger({ plugin: "slack", feature: "thread-participation-state" })
50+.warn("Slack persistent thread participation state failed", { error: String(error) });
51+} catch {
52+// Best effort only: persistent state must never break Slack message handling.
53+}
54+}
55+56+function disablePersistentThreadParticipation(error: unknown): void {
57+persistentStoreDisabled = true;
58+persistentStore = undefined;
59+reportPersistentThreadParticipationError(error);
60+}
61+62+function getPersistentThreadParticipationStore(): SlackThreadParticipationStore | undefined {
63+if (persistentStoreDisabled) {
64+return undefined;
65+}
66+if (persistentStore) {
67+return persistentStore;
68+}
69+const runtime = getOptionalSlackRuntime();
70+if (!runtime) {
71+return undefined;
72+}
73+try {
74+persistentStore = runtime.state.openKeyedStore<SlackThreadParticipationRecord>({
75+namespace: PERSISTENT_NAMESPACE,
76+maxEntries: PERSISTENT_MAX_ENTRIES,
77+defaultTtlMs: TTL_MS,
78+});
79+return persistentStore;
80+} catch (error) {
81+disablePersistentThreadParticipation(error);
82+return undefined;
83+}
84+}
85+86+function rememberPersistentThreadParticipation(params: { key: string; agentId?: string }): void {
87+const store = getPersistentThreadParticipationStore();
88+if (!store) {
89+return;
90+}
91+void store
92+.register(params.key, {
93+// Stored for future per-agent thread routing; current reads only need presence.
94+ ...(params.agentId ? { agentId: params.agentId } : {}),
95+repliedAt: Date.now(),
96+})
97+.catch(disablePersistentThreadParticipation);
98+}
99+100+async function lookupPersistentThreadParticipation(key: string): Promise<boolean> {
101+const store = getPersistentThreadParticipationStore();
102+if (!store) {
103+return false;
104+}
105+try {
106+return Boolean(await store.lookup(key));
107+} catch (error) {
108+disablePersistentThreadParticipation(error);
109+return false;
110+}
111+}
112+26113export function recordSlackThreadParticipation(
27114accountId: string,
28115channelId: string,
29116threadTs: string,
117+opts?: { agentId?: string },
30118): void {
31119if (!accountId || !channelId || !threadTs) {
32120return;
33121}
34-threadParticipation.check(makeKey(accountId, channelId, threadTs));
122+const key = makeKey(accountId, channelId, threadTs);
123+threadParticipation.check(key);
124+rememberPersistentThreadParticipation({ key, agentId: opts?.agentId });
35125}
3612637127export function hasSlackThreadParticipation(
@@ -45,6 +135,27 @@ export function hasSlackThreadParticipation(
45135return threadParticipation.peek(makeKey(accountId, channelId, threadTs));
46136}
47137138+export async function hasSlackThreadParticipationWithPersistence(params: {
139+accountId: string;
140+channelId: string;
141+threadTs: string;
142+}): Promise<boolean> {
143+if (!params.accountId || !params.channelId || !params.threadTs) {
144+return false;
145+}
146+const key = makeKey(params.accountId, params.channelId, params.threadTs);
147+if (threadParticipation.peek(key)) {
148+return true;
149+}
150+const found = await lookupPersistentThreadParticipation(key);
151+if (found) {
152+threadParticipation.check(key);
153+}
154+return found;
155+}
156+48157export function clearSlackThreadParticipationCache(): void {
49158threadParticipation.clear();
159+persistentStore = undefined;
160+persistentStoreDisabled = false;
50161}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。