From 5caa5a047e3f98e1a87ca083a499c2f92dcbc1ac Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Thu, 26 Feb 2026 21:41:56 +0100
Subject: [PATCH] Prevent historical Teams chat replay as fresh transcript
input.
Add chat warmup guard and key-based deduplication so old thread messages are not re-emitted with new timestamps after join.
Made-with: Cursor
---
src/bot/chatProcedure.ts | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/bot/chatProcedure.ts b/src/bot/chatProcedure.ts
index 44c44f5..20ff466 100644
--- a/src/bot/chatProcedure.ts
+++ b/src/bot/chatProcedure.ts
@@ -20,6 +20,8 @@ export class ChatProcedure {
private _onChatMessage: (entry: ChatMessageEntry) => void;
private _isSubscribed: boolean = false;
private _lastMessageText: string = '';
+ private _chatReadyAtMs: number = 0;
+ private _recentMessageKeyTimestamps: Map = new Map();
constructor(
page: Page,
@@ -119,6 +121,7 @@ export class ChatProcedure {
speaker: string;
text: string;
timestamp: string;
+ messageKey?: string;
}) => {
this._handleChatMessage(msg);
});
@@ -204,6 +207,7 @@ export class ChatProcedure {
speaker: author,
text,
timestamp: new Date().toISOString(),
+ messageKey: `${author}::${text}`,
});
return true;
}
@@ -239,6 +243,7 @@ export class ChatProcedure {
speaker: candidateName,
text: candidateBody,
timestamp: new Date().toISOString(),
+ messageKey: `${candidateName}::${candidateBody}`,
});
return true;
}
@@ -320,16 +325,32 @@ export class ChatProcedure {
});
this._logger.info(`Chat MutationObserver set up (target: ${chatObserverTarget})`);
+ // Guard against Teams replaying historical thread entries as fresh mutations.
+ this._chatReadyAtMs = Date.now() + 12000;
+ this._logger.info('Chat monitor warmup active for 12s');
}
/**
* Handle a chat message event from the browser.
*/
- private _handleChatMessage(msg: { speaker: string; text: string; timestamp: string }): void {
+ private _handleChatMessage(msg: { speaker: string; text: string; timestamp: string; messageKey?: string }): void {
if (!this._isSubscribed || !msg.text) return;
+ const nowMs = Date.now();
+ if (this._chatReadyAtMs > 0 && nowMs < this._chatReadyAtMs) return;
+
// Dedup
if (msg.text === this._lastMessageText) return;
+ const key = msg.messageKey || `${msg.speaker}::${msg.text}`;
+ const lastSeen = this._recentMessageKeyTimestamps.get(key);
+ if (lastSeen && (nowMs - lastSeen) < 120000) return;
+ this._recentMessageKeyTimestamps.set(key, nowMs);
+ if (this._recentMessageKeyTimestamps.size > 400) {
+ const cutoff = nowMs - 10 * 60 * 1000;
+ for (const [k, ts] of this._recentMessageKeyTimestamps.entries()) {
+ if (ts < cutoff) this._recentMessageKeyTimestamps.delete(k);
+ }
+ }
this._lastMessageText = msg.text;
this._logger.info(`Chat: [${msg.speaker}] ${msg.text}`);