feat(session): add daily reset policy

Co-authored-by: Austin Mudd <austinm911@gmail.com>
This commit is contained in:
Peter Steinberger
2026-01-18 06:37:30 +00:00
parent f03c3b3f05
commit 367826f6e4
17 changed files with 425 additions and 38 deletions

View File

@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../../config/config.js";
import { saveSessionStore } from "../../config/sessions.js";
@@ -170,3 +170,141 @@ describe("initSessionState RawBody", () => {
expect(result.triggerBodyNormalized).toBe("/status");
});
});
describe("initSessionState reset policy", () => {
it("defaults to daily reset at 4am local time", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date(2026, 0, 18, 5, 0, 0));
try {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reset-daily-"));
const storePath = path.join(root, "sessions.json");
const sessionKey = "agent:main:whatsapp:dm:S1";
const existingSessionId = "daily-session-id";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: new Date(2026, 0, 18, 3, 0, 0).getTime(),
},
});
const cfg = { session: { store: storePath } } as ClawdbotConfig;
const result = await initSessionState({
ctx: { Body: "hello", SessionKey: sessionKey },
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(true);
expect(result.sessionId).not.toBe(existingSessionId);
} finally {
vi.useRealTimers();
}
});
it("expires sessions when idle timeout wins over daily reset", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date(2026, 0, 18, 5, 30, 0));
try {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reset-idle-"));
const storePath = path.join(root, "sessions.json");
const sessionKey = "agent:main:whatsapp:dm:S2";
const existingSessionId = "idle-session-id";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: new Date(2026, 0, 18, 4, 45, 0).getTime(),
},
});
const cfg = {
session: {
store: storePath,
reset: { mode: "daily", atHour: 4, idleMinutes: 30 },
},
} as ClawdbotConfig;
const result = await initSessionState({
ctx: { Body: "hello", SessionKey: sessionKey },
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(true);
expect(result.sessionId).not.toBe(existingSessionId);
} finally {
vi.useRealTimers();
}
});
it("uses per-type overrides for thread sessions", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date(2026, 0, 18, 5, 0, 0));
try {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reset-thread-"));
const storePath = path.join(root, "sessions.json");
const sessionKey = "agent:main:slack:channel:C1:thread:123";
const existingSessionId = "thread-session-id";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: new Date(2026, 0, 18, 3, 0, 0).getTime(),
},
});
const cfg = {
session: {
store: storePath,
reset: { mode: "daily", atHour: 4 },
resetByType: { thread: { mode: "idle", idleMinutes: 180 } },
},
} as ClawdbotConfig;
const result = await initSessionState({
ctx: { Body: "reply", SessionKey: sessionKey, ThreadLabel: "Slack thread" },
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(false);
expect(result.sessionId).toBe(existingSessionId);
} finally {
vi.useRealTimers();
}
});
it("keeps legacy idleMinutes behavior without reset config", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date(2026, 0, 18, 5, 0, 0));
try {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reset-legacy-"));
const storePath = path.join(root, "sessions.json");
const sessionKey = "agent:main:whatsapp:dm:S3";
const existingSessionId = "legacy-session-id";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: new Date(2026, 0, 18, 3, 30, 0).getTime(),
},
});
const cfg = {
session: {
store: storePath,
idleMinutes: 240,
},
} as ClawdbotConfig;
const result = await initSessionState({
ctx: { Body: "hello", SessionKey: sessionKey },
cfg,
commandAuthorized: true,
});
expect(result.isNewSession).toBe(false);
expect(result.sessionId).toBe(existingSessionId);
} finally {
vi.useRealTimers();
}
});
});