- Add gitignore and prettier configuration files for project scaffolding - Implement room access control service with project member verification - Create user access key management with CRUD operations and activity logging - Add accordion UI component for frontend expandable sections - Implement room AI configuration with list, upsert, and delete operations - Add AI event types for agent join/leave/status change tracking - Create streaming AI processing services for mode and react patterns - Build room AI service with model detection and idempotency handling - Integrate chat service orchestration for AI message processing - Add typing indicators and stream cancellation for AI interactions - Implement mention parsing and context extraction for AI agents
67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
/**
|
|
* Deduplication manager — prevents processing the same event twice
|
|
* within a configurable window. Matches Rust `DeduplicationManager`
|
|
* which uses Redis with 5-minute TTL.
|
|
*/
|
|
import { DEDUP_WINDOW_MS } from './constants';
|
|
import type { Uuid } from './types/core';
|
|
|
|
interface DedupEntry {
|
|
timestamp: number;
|
|
}
|
|
|
|
export class DedupManager {
|
|
private entries: Map<string, DedupEntry> = new Map();
|
|
private windowMs = DEDUP_WINDOW_MS;
|
|
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
|
|
constructor() {
|
|
// Periodic cleanup of expired entries (every 30s)
|
|
this.cleanupInterval = setInterval(() => this.cleanup(), 30_000);
|
|
}
|
|
|
|
/** Check if a message is a duplicate. Returns false if new, true if duplicate. */
|
|
checkAndMark(messageId: Uuid, roomId: Uuid): boolean {
|
|
const key = `${roomId}:${messageId}`;
|
|
const existing = this.entries.get(key);
|
|
if (existing) return true; // duplicate
|
|
|
|
this.entries.set(key, { timestamp: Date.now() });
|
|
return false; // not a duplicate
|
|
}
|
|
|
|
/** Check if a message is already known (without marking it). */
|
|
isDuplicate(messageId: Uuid, roomId: Uuid): boolean {
|
|
const key = `${roomId}:${messageId}`;
|
|
return this.entries.has(key);
|
|
}
|
|
|
|
/** Remove expired entries. */
|
|
private cleanup(): void {
|
|
const now = Date.now();
|
|
for (const [key, entry] of this.entries) {
|
|
if (now - entry.timestamp > this.windowMs) {
|
|
this.entries.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Clear all dedup entries. */
|
|
clear(): void {
|
|
this.entries.clear();
|
|
}
|
|
|
|
/** Stop the cleanup interval. */
|
|
destroy(): void {
|
|
if (this.cleanupInterval) {
|
|
clearInterval(this.cleanupInterval);
|
|
this.cleanupInterval = null;
|
|
}
|
|
this.entries.clear();
|
|
}
|
|
|
|
/** Get current entry count. */
|
|
get size(): number {
|
|
return this.entries.size;
|
|
}
|
|
} |