From 2df34387e4f3bd772e7afa588e8fe1fd71d76820 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 16 May 2026 08:58:01 +0100 Subject: [PATCH] fix: preserve session send database path --- src/gateway/server-methods/sessions.ts | 17 +++++-- src/gateway/server-session-events.ts | 10 +++- .../server.chat.gateway-server-chat.test.ts | 47 ++++++++++++++++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/gateway/server-methods/sessions.ts b/src/gateway/server-methods/sessions.ts index 919258e2589..57d026c8e35 100644 --- a/src/gateway/server-methods/sessions.ts +++ b/src/gateway/server-methods/sessions.ts @@ -477,7 +477,7 @@ async function createAgentMainSessionForSend(params: { canonicalKey: string; context: GatewayRequestContext; }): Promise< - | { ok: true; canonicalKey: string; entry: SessionEntry } + | { ok: true; agentId: string; databasePath: string; canonicalKey: string; entry: SessionEntry } | { ok: false; error: ReturnType } > { const target = resolveGatewaySessionDatabaseTarget({ @@ -515,7 +515,13 @@ async function createAgentMainSessionForSend(params: { ), }; } - return { ok: true, canonicalKey: target.canonicalKey, entry: patched.entry }; + return { + ok: true, + agentId: target.agentId, + databasePath: target.databasePath, + canonicalKey: target.canonicalKey, + entry: patched.entry, + }; } function resolveAbortSessionKey(params: { @@ -721,6 +727,8 @@ async function handleSessionSend(params: { const cfg = loadedSession.cfg; let entry = loadedSession.entry; let canonicalKey = loadedSession.canonicalKey; + let agentId = loadedSession.agentId; + let databasePath = resolveGatewaySessionDatabaseTarget({ cfg, key: canonicalKey }).databasePath; // Reject sends/steers targeting sessions whose owning agent was deleted (#65524). const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, canonicalKey); if (deletedAgentId !== null) { @@ -746,6 +754,8 @@ async function handleSessionSend(params: { } entry = created.entry; canonicalKey = created.canonicalKey; + agentId = created.agentId; + databasePath = created.databasePath; } if (!entry?.sessionId) { params.respond( @@ -776,7 +786,8 @@ async function handleSessionSend(params: { const messageSeq = (await readSessionMessageCountAsync({ - agentId: resolveAgentIdFromSessionKey(canonicalKey), + agentId, + path: databasePath, sessionId: entry.sessionId, })) + 1; let sendAcked = false; diff --git a/src/gateway/server-session-events.ts b/src/gateway/server-session-events.ts index adf88e3e1d2..ff8e4758de7 100644 --- a/src/gateway/server-session-events.ts +++ b/src/gateway/server-session-events.ts @@ -14,6 +14,7 @@ import { loadGatewaySessionRow, loadSessionEntry, readSessionMessageCountAsync, + resolveGatewaySessionDatabaseTarget, type GatewaySessionRow, } from "./session-utils.js"; @@ -127,12 +128,17 @@ async function handleTranscriptUpdateBroadcast( if (connIds.size === 0) { return; } - const { entry } = loadSessionEntry(sessionKey); + const { cfg, entry } = loadSessionEntry(sessionKey); const agentId = resolveAgentIdFromSessionKey(sessionKey); + const databasePath = resolveGatewaySessionDatabaseTarget({ cfg, key: sessionKey }).databasePath; const messageSeq = asPositiveSafeInteger(update.messageSeq) ?? (entry?.sessionId && agentId - ? await readSessionMessageCountAsync({ agentId, sessionId: entry.sessionId }) + ? await readSessionMessageCountAsync({ + agentId, + path: databasePath, + sessionId: entry.sessionId, + }) : undefined); const sessionSnapshot = buildGatewaySessionSnapshot({ sessionRow: loadGatewaySessionRow(sessionKey, { transcriptUsageMaxBytes: 64 * 1024 }), diff --git a/src/gateway/server.chat.gateway-server-chat.test.ts b/src/gateway/server.chat.gateway-server-chat.test.ts index b2047e62b33..ab5d8c96584 100644 --- a/src/gateway/server.chat.gateway-server-chat.test.ts +++ b/src/gateway/server.chat.gateway-server-chat.test.ts @@ -3,10 +3,11 @@ import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, test, vi } from "vitest"; import { WebSocket } from "ws"; -import { getSessionEntry } from "../config/sessions.js"; +import { getSessionEntry, upsertSessionEntry } from "../config/sessions.js"; import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js"; import { emitAgentEvent, registerAgentRunContext } from "../infra/agent-events.js"; import { extractFirstTextBlock } from "../shared/chat-message-content.js"; +import { openOpenClawAgentDatabase } from "../state/openclaw-agent-db.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; import { testing as agentJobTesting } from "./server-methods/agent-job.js"; import { @@ -265,6 +266,50 @@ describe("gateway server chat", () => { } }); + test("sessions.send counts messages from selected registered database path", async () => { + const stateDir = process.env.OPENCLAW_STATE_DIR; + if (!stateDir) { + throw new Error("OPENCLAW_STATE_DIR missing in gateway test environment"); + } + const databasePath = path.join(stateDir, "relocated", "send.sqlite"); + openOpenClawAgentDatabase({ agentId: "main", path: databasePath }); + upsertSessionEntry({ + agentId: "main", + path: databasePath, + sessionKey: "agent:main:dashboard:registered-send", + entry: { + sessionId: "sess-registered-send", + updatedAt: Date.now(), + }, + }); + replaceSqliteSessionTranscriptEvents({ + agentId: "main", + path: databasePath, + sessionId: "sess-registered-send", + events: [ + { type: "session", version: 1, id: "sess-registered-send" }, + { + type: "message", + id: "msg-1", + message: { role: "user", content: "first" }, + }, + { + type: "message", + id: "msg-2", + message: { role: "assistant", content: "second" }, + }, + ], + }); + + const res = await rpcReq(ws, "sessions.send", { + key: "agent:main:dashboard:registered-send", + message: "hello relocated", + idempotencyKey: "idem-sessions-send-registered-db", + }); + expect(res.ok).toBe(true); + expect(res.payload?.messageSeq).toBe(3); + }); + test("sessions.send creates a configured agent main session before sending", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-send-agent-")); testState.sessionStorePath = path.join(dir, "sessions.json");