mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:38:38 +00:00
Cron+Slack: fix cooldown omission and cache cap enforcement
This commit is contained in:
@@ -55,4 +55,13 @@ describe("slack sent-thread-cache", () => {
|
|||||||
vi.spyOn(Date, "now").mockReturnValue(Date.now() + 25 * 60 * 60 * 1000);
|
vi.spyOn(Date, "now").mockReturnValue(Date.now() + 25 * 60 * 60 * 1000);
|
||||||
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
|
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enforces maximum entries by evicting oldest fresh entries", () => {
|
||||||
|
for (let i = 0; i < 5001; i += 1) {
|
||||||
|
recordSlackThreadParticipation("A1", "C123", `1700000000.${String(i).padStart(6, "0")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000000")).toBe(false);
|
||||||
|
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.005000")).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ function evictExpired(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function evictOldest(): void {
|
||||||
|
const oldest = threadParticipation.keys().next().value;
|
||||||
|
if (oldest) {
|
||||||
|
threadParticipation.delete(oldest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function recordSlackThreadParticipation(
|
export function recordSlackThreadParticipation(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
channelId: string,
|
channelId: string,
|
||||||
@@ -33,6 +40,9 @@ export function recordSlackThreadParticipation(
|
|||||||
if (threadParticipation.size >= MAX_ENTRIES) {
|
if (threadParticipation.size >= MAX_ENTRIES) {
|
||||||
evictExpired();
|
evictExpired();
|
||||||
}
|
}
|
||||||
|
if (threadParticipation.size >= MAX_ENTRIES) {
|
||||||
|
evictOldest();
|
||||||
|
}
|
||||||
threadParticipation.set(makeKey(accountId, channelId, threadTs), Date.now());
|
threadParticipation.set(makeKey(accountId, channelId, threadTs), Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -346,6 +346,55 @@ describe("cron controller", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("omits failureAlert.cooldownMs when custom cooldown is left blank", async () => {
|
||||||
|
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||||
|
if (method === "cron.update") {
|
||||||
|
return { id: "job-alert-no-cooldown" };
|
||||||
|
}
|
||||||
|
if (method === "cron.list") {
|
||||||
|
return { jobs: [{ id: "job-alert-no-cooldown" }] };
|
||||||
|
}
|
||||||
|
if (method === "cron.status") {
|
||||||
|
return { enabled: true, jobs: 1, nextWakeAtMs: null };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
const state = createState({
|
||||||
|
client: { request } as unknown as CronState["client"],
|
||||||
|
cronEditingJobId: "job-alert-no-cooldown",
|
||||||
|
cronForm: {
|
||||||
|
...DEFAULT_CRON_FORM,
|
||||||
|
name: "alert job no cooldown",
|
||||||
|
payloadKind: "agentTurn",
|
||||||
|
payloadText: "run it",
|
||||||
|
failureAlertMode: "custom",
|
||||||
|
failureAlertAfter: "3",
|
||||||
|
failureAlertCooldownSeconds: "",
|
||||||
|
failureAlertChannel: "telegram",
|
||||||
|
failureAlertTo: "123456",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await addCronJob(state);
|
||||||
|
|
||||||
|
const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
|
||||||
|
expect(updateCall).toBeDefined();
|
||||||
|
expect(updateCall?.[1]).toMatchObject({
|
||||||
|
id: "job-alert-no-cooldown",
|
||||||
|
patch: {
|
||||||
|
failureAlert: {
|
||||||
|
after: 3,
|
||||||
|
channel: "telegram",
|
||||||
|
to: "123456",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
(updateCall?.[1] as { patch?: { failureAlert?: { cooldownMs?: number } } })?.patch
|
||||||
|
?.failureAlert,
|
||||||
|
).not.toHaveProperty("cooldownMs");
|
||||||
|
});
|
||||||
|
|
||||||
it("includes failureAlert=false when disabled per job", async () => {
|
it("includes failureAlert=false when disabled per job", async () => {
|
||||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||||
if (method === "cron.update") {
|
if (method === "cron.update") {
|
||||||
|
|||||||
@@ -579,15 +579,17 @@ function buildFailureAlert(form: CronFormState) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const after = toNumber(form.failureAlertAfter.trim(), 0);
|
const after = toNumber(form.failureAlertAfter.trim(), 0);
|
||||||
const cooldownSeconds = toNumber(form.failureAlertCooldownSeconds.trim(), 0);
|
const cooldownRaw = form.failureAlertCooldownSeconds.trim();
|
||||||
|
const cooldownSeconds = cooldownRaw.length > 0 ? toNumber(cooldownRaw, 0) : undefined;
|
||||||
|
const cooldownMs =
|
||||||
|
cooldownSeconds !== undefined && Number.isFinite(cooldownSeconds) && cooldownSeconds >= 0
|
||||||
|
? Math.floor(cooldownSeconds * 1000)
|
||||||
|
: undefined;
|
||||||
return {
|
return {
|
||||||
after: after > 0 ? Math.floor(after) : undefined,
|
after: after > 0 ? Math.floor(after) : undefined,
|
||||||
channel: form.failureAlertChannel.trim() || CRON_CHANNEL_LAST,
|
channel: form.failureAlertChannel.trim() || CRON_CHANNEL_LAST,
|
||||||
to: form.failureAlertTo.trim() || undefined,
|
to: form.failureAlertTo.trim() || undefined,
|
||||||
cooldownMs:
|
...(cooldownMs !== undefined ? { cooldownMs } : {}),
|
||||||
Number.isFinite(cooldownSeconds) && cooldownSeconds >= 0
|
|
||||||
? Math.floor(cooldownSeconds * 1000)
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user