mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-05 20:12:52 +00:00
fix(telegram): bound error cooldown expiry
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { MAX_DATE_TIMESTAMP_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildTelegramErrorScopeKey,
|
||||
@@ -120,6 +121,72 @@ describe("telegram error policy", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not suppress or keep cooldowns when the process clock is invalid", () => {
|
||||
const scopeKey = buildTelegramErrorScopeKey({
|
||||
accountId: "work",
|
||||
chatId: 42,
|
||||
});
|
||||
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
|
||||
try {
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
nowSpy.mockRestore();
|
||||
}
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not store cooldowns whose expiry would exceed the Date range", () => {
|
||||
const scopeKey = buildTelegramErrorScopeKey({
|
||||
accountId: "work",
|
||||
chatId: 42,
|
||||
});
|
||||
vi.setSystemTime(MAX_DATE_TIMESTAMP_MS);
|
||||
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(false);
|
||||
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
|
||||
expect(
|
||||
shouldSuppressTelegramError({
|
||||
scopeKey,
|
||||
cooldownMs: 1000,
|
||||
errorMessage: "429",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not leak suppression across accounts or threads", () => {
|
||||
const workMain = buildTelegramErrorScopeKey({
|
||||
accountId: "work",
|
||||
|
||||
@@ -4,6 +4,11 @@ import type {
|
||||
TelegramGroupConfig,
|
||||
TelegramTopicConfig,
|
||||
} from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
isFutureDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
type TelegramErrorPolicy = "always" | "once" | "silent";
|
||||
|
||||
@@ -18,7 +23,7 @@ const DEFAULT_ERROR_COOLDOWN_MS = 14400000;
|
||||
|
||||
function pruneExpiredCooldowns(messageStore: Map<string, number>, now: number) {
|
||||
for (const [message, expiresAt] of messageStore) {
|
||||
if (expiresAt <= now) {
|
||||
if (!isFutureDateTimestampMs(expiresAt, { nowMs: now })) {
|
||||
messageStore.delete(message);
|
||||
}
|
||||
}
|
||||
@@ -67,9 +72,13 @@ export function shouldSuppressTelegramError(params: {
|
||||
errorMessage?: string;
|
||||
}): boolean {
|
||||
const { scopeKey, cooldownMs, errorMessage } = params;
|
||||
const now = Date.now();
|
||||
const now = asDateTimestampMs(Date.now());
|
||||
const messageKey = errorMessage ?? "";
|
||||
const scopeStore = errorCooldownStore.get(scopeKey);
|
||||
if (now === undefined) {
|
||||
errorCooldownStore.delete(scopeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scopeStore) {
|
||||
pruneExpiredCooldowns(scopeStore, now);
|
||||
@@ -88,12 +97,17 @@ export function shouldSuppressTelegramError(params: {
|
||||
}
|
||||
|
||||
const expiresAt = scopeStore?.get(messageKey);
|
||||
if (typeof expiresAt === "number" && expiresAt > now) {
|
||||
if (isFutureDateTimestampMs(expiresAt, { nowMs: now })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const nextExpiresAt = resolveExpiresAtMsFromDurationMs(cooldownMs, { nowMs: now });
|
||||
if (nextExpiresAt === undefined) {
|
||||
scopeStore?.delete(messageKey);
|
||||
return false;
|
||||
}
|
||||
const nextScopeStore = scopeStore ?? new Map<string, number>();
|
||||
nextScopeStore.set(messageKey, now + cooldownMs);
|
||||
nextScopeStore.set(messageKey, nextExpiresAt);
|
||||
errorCooldownStore.set(scopeKey, nextScopeStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user