























@@ -1,7 +1,22 @@
1-const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
1+import { getOptionalMSTeamsRuntime } from "./runtime.js";
2+3+const TTL_MS = 24 * 60 * 60 * 1000;
4+const PERSISTENT_MAX_ENTRIES = 1000;
5+const PERSISTENT_NAMESPACE = "msteams.sent-messages";
26const MSTEAMS_SENT_MESSAGES_KEY = Symbol.for("openclaw.msteamsSentMessages");
378+type MSTeamsSentMessageRecord = {
9+sentAt: number;
10+};
11+12+type MSTeamsSentMessageStore = {
13+register(key: string, value: MSTeamsSentMessageRecord, opts?: { ttlMs?: number }): Promise<void>;
14+lookup(key: string): Promise<MSTeamsSentMessageRecord | undefined>;
15+};
16+417let sentMessageCache: Map<string, Map<string, number>> | undefined;
18+let persistentStore: MSTeamsSentMessageStore | undefined;
19+let persistentStoreDisabled = false;
520621function getSentMessageCache(): Map<string, Map<string, number>> {
722if (!sentMessageCache) {
@@ -14,6 +29,50 @@ function getSentMessageCache(): Map<string, Map<string, number>> {
1429return sentMessageCache;
1530}
163132+function makePersistentKey(conversationId: string, messageId: string): string {
33+return `${conversationId}:${messageId}`;
34+}
35+36+function reportPersistentSentMessageError(error: unknown): void {
37+try {
38+getOptionalMSTeamsRuntime()
39+?.logging.getChildLogger({ plugin: "msteams", feature: "sent-message-state" })
40+.warn("Microsoft Teams persistent sent-message state failed", { error: String(error) });
41+} catch {
42+// Best effort only: persistent state must never break Teams routing.
43+}
44+}
45+46+function disablePersistentSentMessageStore(error: unknown): void {
47+persistentStoreDisabled = true;
48+persistentStore = undefined;
49+reportPersistentSentMessageError(error);
50+}
51+52+function getPersistentSentMessageStore(): MSTeamsSentMessageStore | undefined {
53+if (persistentStoreDisabled) {
54+return undefined;
55+}
56+if (persistentStore) {
57+return persistentStore;
58+}
59+const runtime = getOptionalMSTeamsRuntime();
60+if (!runtime) {
61+return undefined;
62+}
63+try {
64+persistentStore = runtime.state.openKeyedStore<MSTeamsSentMessageRecord>({
65+namespace: PERSISTENT_NAMESPACE,
66+maxEntries: PERSISTENT_MAX_ENTRIES,
67+defaultTtlMs: TTL_MS,
68+});
69+return persistentStore;
70+} catch (error) {
71+disablePersistentSentMessageStore(error);
72+return undefined;
73+}
74+}
75+1776function cleanupExpired(scopeKey: string, entry: Map<string, number>, now: number): void {
1877for (const [id, timestamp] of entry) {
1978if (now - timestamp > TTL_MS) {
@@ -25,23 +84,62 @@ function cleanupExpired(scopeKey: string, entry: Map<string, number>, now: numbe
2584}
2685}
278628-export function recordMSTeamsSentMessage(conversationId: string, messageId: string): void {
29-if (!conversationId || !messageId) {
30- return;
31-}
32- const now = Date.now();
87+function rememberSentMessageInMemory(
88+conversationId: string,
89+messageId: string,
90+sentAt: number,
91+): void {
3392const store = getSentMessageCache();
3493let entry = store.get(conversationId);
3594if (!entry) {
3695entry = new Map<string, number>();
3796store.set(conversationId, entry);
3897}
39-entry.set(messageId, now);
98+entry.set(messageId, sentAt);
4099if (entry.size > 200) {
41-cleanupExpired(conversationId, entry, now);
100+cleanupExpired(conversationId, entry, sentAt);
42101}
43102}
44103104+function rememberPersistentSentMessage(params: {
105+conversationId: string;
106+messageId: string;
107+sentAt: number;
108+}): void {
109+const store = getPersistentSentMessageStore();
110+if (!store) {
111+return;
112+}
113+void store
114+.register(makePersistentKey(params.conversationId, params.messageId), { sentAt: params.sentAt })
115+.catch(disablePersistentSentMessageStore);
116+}
117+118+async function lookupPersistentSentMessage(params: {
119+conversationId: string;
120+messageId: string;
121+}): Promise<number | undefined> {
122+const store = getPersistentSentMessageStore();
123+if (!store) {
124+return undefined;
125+}
126+try {
127+return (await store.lookup(makePersistentKey(params.conversationId, params.messageId)))?.sentAt;
128+} catch (error) {
129+disablePersistentSentMessageStore(error);
130+return undefined;
131+}
132+}
133+134+export function recordMSTeamsSentMessage(conversationId: string, messageId: string): void {
135+if (!conversationId || !messageId) {
136+return;
137+}
138+const now = Date.now();
139+rememberSentMessageInMemory(conversationId, messageId, now);
140+rememberPersistentSentMessage({ conversationId, messageId, sentAt: now });
141+}
142+45143export function wasMSTeamsMessageSent(conversationId: string, messageId: string): boolean {
46144const entry = getSentMessageCache().get(conversationId);
47145if (!entry) {
@@ -51,6 +149,26 @@ export function wasMSTeamsMessageSent(conversationId: string, messageId: string)
51149return entry.has(messageId);
52150}
53151152+export async function wasMSTeamsMessageSentWithPersistence(params: {
153+conversationId: string;
154+messageId: string;
155+}): Promise<boolean> {
156+if (!params.conversationId || !params.messageId) {
157+return false;
158+}
159+if (wasMSTeamsMessageSent(params.conversationId, params.messageId)) {
160+return true;
161+}
162+const sentAt = await lookupPersistentSentMessage(params);
163+if (sentAt == null) {
164+return false;
165+}
166+rememberSentMessageInMemory(params.conversationId, params.messageId, sentAt);
167+return wasMSTeamsMessageSent(params.conversationId, params.messageId);
168+}
169+54170export function clearMSTeamsSentMessageCache(): void {
55171getSentMessageCache().clear();
172+persistentStore = undefined;
173+persistentStoreDisabled = false;
56174}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。