From 1b81805d6346037aaaa4baaadcf7e087a080c46c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 7 Jan 2026 07:14:24 +0000 Subject: [PATCH] fix: align heartbeat session store with default agent --- src/infra/heartbeat-runner.test.ts | 64 ++++++++++++++++++++++++++++++ src/infra/heartbeat-runner.ts | 8 ++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/infra/heartbeat-runner.test.ts b/src/infra/heartbeat-runner.test.ts index 087249bba..f73ab120d 100644 --- a/src/infra/heartbeat-runner.test.ts +++ b/src/infra/heartbeat-runner.test.ts @@ -184,6 +184,70 @@ describe("runHeartbeatOnce", () => { } }); + it("loads the default agent session from templated stores", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-hb-")); + const storeTemplate = path.join( + tmpDir, + "agents", + "{agentId}", + "sessions.json", + ); + const storePath = path.join(tmpDir, "agents", "work", "sessions.json"); + const replySpy = vi.spyOn(replyModule, "getReplyFromConfig"); + try { + await fs.mkdir(path.dirname(storePath), { recursive: true }); + await fs.writeFile( + storePath, + JSON.stringify( + { + "agent:work:main": { + sessionId: "sid", + updatedAt: Date.now(), + lastProvider: "whatsapp", + lastTo: "+1555", + }, + }, + null, + 2, + ), + ); + + const cfg: ClawdbotConfig = { + routing: { defaultAgentId: "work" }, + agent: { heartbeat: { every: "5m" } }, + whatsapp: { allowFrom: ["*"] }, + session: { store: storeTemplate }, + }; + + replySpy.mockResolvedValue({ text: "Hello from heartbeat" }); + const sendWhatsApp = vi.fn().mockResolvedValue({ + messageId: "m1", + toJid: "jid", + }); + + await runHeartbeatOnce({ + cfg, + deps: { + sendWhatsApp, + getQueueSize: () => 0, + nowMs: () => 0, + webAuthExists: async () => true, + hasActiveWebListener: () => true, + }, + }); + + expect(sendWhatsApp).toHaveBeenCalledTimes(1); + expect(sendWhatsApp).toHaveBeenCalledWith( + "+1555", + "Hello from heartbeat", + expect.any(Object), + ); + } finally { + replySpy.mockRestore(); + await fs.rm(tmpDir, { recursive: true, force: true }); + } + }); + it("respects ackMaxChars for heartbeat acks", async () => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-hb-")); const storePath = path.join(tmpDir, "sessions.json"); diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index e91d0f7cf..b015a0896 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -11,6 +11,8 @@ import type { ClawdbotConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js"; import { loadSessionStore, + resolveAgentIdFromSessionKey, + resolveMainSessionKey, resolveStorePath, type SessionEntry, saveSessionStore, @@ -79,9 +81,9 @@ function resolveHeartbeatAckMaxChars(cfg: ClawdbotConfig) { function resolveHeartbeatSession(cfg: ClawdbotConfig) { const sessionCfg = cfg.session; const scope = sessionCfg?.scope ?? "per-sender"; - const mainKey = (sessionCfg?.mainKey ?? "main").trim() || "main"; - const sessionKey = scope === "global" ? "global" : mainKey; - const storePath = resolveStorePath(sessionCfg?.store); + const sessionKey = scope === "global" ? "global" : resolveMainSessionKey(cfg); + const agentId = resolveAgentIdFromSessionKey(sessionKey); + const storePath = resolveStorePath(sessionCfg?.store, { agentId }); const store = loadSessionStore(storePath); const entry = store[sessionKey]; return { sessionKey, storePath, store, entry };