diff --git a/extensions/telegram/src/bot-native-commands.session-meta.test.ts b/extensions/telegram/src/bot-native-commands.session-meta.test.ts index 3b2c6f00eb3..905a17caccc 100644 --- a/extensions/telegram/src/bot-native-commands.session-meta.test.ts +++ b/extensions/telegram/src/bot-native-commands.session-meta.test.ts @@ -1,4 +1,3 @@ -import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -55,7 +54,6 @@ const sessionMocks = vi.hoisted(() => { ), recordSessionMetaFromInbound: vi.fn(), resolveAndPersistSessionFile: vi.fn(), - resolveSessionTranscriptPath: vi.fn(), sessionStore, }; }); @@ -163,7 +161,6 @@ vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => { getSessionEntry: sessionMocks.getSessionEntry, listSessionEntries: sessionMocks.listSessionEntries, resolveAndPersistSessionFile: sessionMocks.resolveAndPersistSessionFile, - resolveSessionTranscriptPath: sessionMocks.resolveSessionTranscriptPath, }; }); vi.mock("openclaw/plugin-sdk/command-auth-native", async () => { @@ -499,13 +496,6 @@ describe("registerTelegramNativeCommands — session metadata", () => { }, }; }); - sessionMocks.resolveSessionTranscriptPath - .mockClear() - .mockImplementation((sessionId: string, _agentId?: string, threadId?: string | number) => - threadId === undefined - ? `/tmp/openclaw-sessions/${sessionId}.jsonl` - : `/tmp/openclaw-sessions/${sessionId}-topic-${threadId}.jsonl`, - ); pluginRuntimeMocks.executePluginCommand.mockClear().mockResolvedValue({ text: "ok" }); pluginRuntimeMocks.matchPluginCommand.mockClear().mockReturnValue(null); replyMocks.dispatchReplyWithBufferedBlockDispatcher @@ -1211,14 +1201,14 @@ describe("registerTelegramNativeCommands — session metadata", () => { expect.objectContaining({ sessionId: "sess-topic", sessionKey: "agent:main:telegram:group:-1001234567890:topic:42", - fallbackSessionFile: path.resolve("/tmp/openclaw-sessions", "sess-topic-topic-42.jsonl"), + fallbackSessionFile: "sqlite-transcript://main/sess-topic.jsonl", }), ); expect(pluginRuntimeMocks.executePluginCommand).toHaveBeenCalledWith( expect.objectContaining({ sessionKey: "agent:main:telegram:group:-1001234567890:topic:42", sessionId: "sess-topic", - sessionFile: path.resolve("/tmp/openclaw-sessions", "sess-topic-topic-42.jsonl"), + sessionFile: "sqlite-transcript://main/sess-topic.jsonl", messageThreadId: 42, }), ); diff --git a/src/agents/btw-transcript.ts b/src/agents/btw-transcript.ts index 2be70fa1a1a..987590d10ef 100644 --- a/src/agents/btw-transcript.ts +++ b/src/agents/btw-transcript.ts @@ -24,7 +24,7 @@ export function resolveBtwSessionTranscriptPath(params: { return createSqliteSessionTranscriptLocator({ agentId, sessionId: params.sessionId }); } catch (error) { diag.debug( - `resolveSessionTranscriptPath failed: sessionId=${params.sessionId} err=${String(error)}`, + `createSqliteSessionTranscriptLocator failed: sessionId=${params.sessionId} err=${String(error)}`, ); return undefined; } diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index 27c58f8bb34..9e275d6a8f1 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -12,11 +12,7 @@ import { resolveSessionKey, updateLastRoute, } from "./sessions.js"; -import { - resolveSessionFilePath, - resolveSessionTranscriptPath, - resolveSessionTranscriptsDir, -} from "./sessions/paths.js"; +import { resolveSessionFilePath, resolveSessionTranscriptsDir } from "./sessions/paths.js"; import { deleteSessionEntry, listSessionEntries, @@ -611,21 +607,6 @@ describe("sessions", () => { expect(dir).toBe(path.join(path.resolve("/custom/state"), "agents", "main", "sessions")); }); - it("includes topic ids in session transcript filenames", () => { - withStateDir("/custom/state", () => { - const sessionFile = resolveSessionTranscriptPath("sess-1", "main", 123); - expect(sessionFile).toBe( - path.join( - path.resolve("/custom/state"), - "agents", - "main", - "sessions", - "sess-1-topic-123.jsonl", - ), - ); - }); - }); - it("uses agent id when resolving session file fallback paths", () => { withStateDir("/custom/state", () => { const sessionFile = resolveSessionFilePath("sess-2", undefined, { diff --git a/src/config/sessions/paths.ts b/src/config/sessions/paths.ts index 5a73f8a53fb..0a7b933c50e 100644 --- a/src/config/sessions/paths.ts +++ b/src/config/sessions/paths.ts @@ -99,33 +99,6 @@ export function isSqliteSessionTranscriptLocator(locator: string | undefined): b return typeof locator === "string" && parseSqliteSessionTranscriptLocator(locator) !== undefined; } -export function resolveSessionTranscriptPathInDir( - sessionId: string, - sessionsDir: string, - topicId?: string | number, -): string { - const safeSessionId = validateSessionId(sessionId); - const safeTopicId = - typeof topicId === "string" - ? encodeURIComponent(topicId) - : typeof topicId === "number" - ? String(topicId) - : undefined; - const fileName = - safeTopicId !== undefined - ? `${safeSessionId}-topic-${safeTopicId}.jsonl` - : `${safeSessionId}.jsonl`; - return path.resolve(sessionsDir, fileName); -} - -export function resolveSessionTranscriptPath( - sessionId: string, - agentId?: string, - topicId?: string | number, -): string { - return resolveSessionTranscriptPathInDir(sessionId, resolveAgentSessionsDir(agentId), topicId); -} - export function resolveSessionFilePath( sessionId: string, entry?: { sessionFile?: string }, diff --git a/src/config/sessions/sessions.test.ts b/src/config/sessions/sessions.test.ts index b0572662d87..4ae551660e7 100644 --- a/src/config/sessions/sessions.test.ts +++ b/src/config/sessions/sessions.test.ts @@ -9,7 +9,6 @@ import { resolveSessionLifecycleTimestamps } from "./lifecycle.js"; import { createSqliteSessionTranscriptLocator, resolveSessionFilePath, - resolveSessionTranscriptPathInDir, validateSessionId, } from "./paths.js"; import { evaluateSessionFreshness, resolveSessionResetPolicy } from "./reset.js"; @@ -38,13 +37,6 @@ describe("session path safety", () => { } }); - it("resolves transcript path inside an explicit sessions dir", () => { - const sessionsDir = "/tmp/openclaw/agents/main/sessions"; - const resolved = resolveSessionTranscriptPathInDir("sess-1", sessionsDir, "topic/a+b"); - - expect(resolved).toBe(path.resolve(sessionsDir, "sess-1-topic-topic%2Fa%2Bb.jsonl")); - }); - it("ignores legacy sessionFile paths", () => { const resolved = resolveSessionFilePath("sess-1", { sessionFile: "/tmp/openclaw/agents/work/not-sessions/abc-123.jsonl", @@ -574,7 +566,7 @@ describe("resolveAndPersistSessionFile", () => { it("creates and persists a SQLite locator when session is not yet present", async () => { const sessionId = "new-session-id"; const sessionKey = "agent:main:telegram:group:123"; - const fallbackSessionFile = resolveSessionTranscriptPathInDir(sessionId, fixture.sessionsDir()); + const fallbackSessionFile = path.join(fixture.sessionsDir(), `${sessionId}.jsonl`); const expectedSessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId, @@ -596,7 +588,7 @@ describe("resolveAndPersistSessionFile", () => { it("normalizes legacy stored transcript paths to SQLite locators", async () => { const sessionId = "legacy-path-session-id"; const sessionKey = "agent:main:telegram:group:456"; - const legacySessionFile = resolveSessionTranscriptPathInDir(sessionId, fixture.sessionsDir()); + const legacySessionFile = path.join(fixture.sessionsDir(), `${sessionId}.jsonl`); const expectedSessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId, @@ -626,10 +618,7 @@ describe("resolveAndPersistSessionFile", () => { const previousSessionId = "old-session-id"; const nextSessionId = "new-session-id"; const sessionKey = "agent:main:telegram:group:123"; - const previousSessionFile = resolveSessionTranscriptPathInDir( - previousSessionId, - fixture.sessionsDir(), - ); + const previousSessionFile = path.join(fixture.sessionsDir(), `${previousSessionId}.jsonl`); const expectedNextSessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId: nextSessionId, diff --git a/src/config/sessions/transcript.test.ts b/src/config/sessions/transcript.test.ts index 2048788d9a6..350c200dd67 100644 --- a/src/config/sessions/transcript.test.ts +++ b/src/config/sessions/transcript.test.ts @@ -8,10 +8,7 @@ import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, } from "../../state/openclaw-state-db.js"; -import { - createSqliteSessionTranscriptLocator, - resolveSessionTranscriptPathInDir, -} from "./paths.js"; +import { createSqliteSessionTranscriptLocator } from "./paths.js"; import { upsertSessionEntry } from "./store.js"; import { useTempSessionsFixture } from "./test-helpers.js"; import { appendSessionTranscriptMessage } from "./transcript-append.js"; @@ -431,7 +428,10 @@ describe("appendAssistantMessageToSessionTranscript", () => { it("serializes concurrent parent-linked transcript appends", async () => { const targetSessionId = "concurrent-tree-session"; - const sessionFile = resolveSessionTranscriptPathInDir(targetSessionId, fixture.sessionsDir()); + const sessionFile = createSqliteSessionTranscriptLocator({ + agentId: "main", + sessionId: targetSessionId, + }); appendSqliteSessionTranscriptEvent({ agentId: "main", sessionId: targetSessionId, @@ -470,7 +470,10 @@ describe("appendAssistantMessageToSessionTranscript", () => { it("appends to existing SQLite transcript chains", async () => { const targetSessionId = "small-linear-session"; - const sessionFile = resolveSessionTranscriptPathInDir(targetSessionId, fixture.sessionsDir()); + const sessionFile = createSqliteSessionTranscriptLocator({ + agentId: "main", + sessionId: targetSessionId, + }); appendSqliteSessionTranscriptEvent({ agentId: "main", sessionId: targetSessionId, @@ -525,10 +528,10 @@ describe("appendAssistantMessageToSessionTranscript", () => { it("appends scoped SQLite transcript entries without importing JSONL at runtime", async () => { const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-transcript-state-")); vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); - const sessionFile = resolveSessionTranscriptPathInDir( - "sqlite-import-session", - fixture.sessionsDir(), - ); + const sessionFile = createSqliteSessionTranscriptLocator({ + agentId: "main", + sessionId: "sqlite-import-session", + }); appendSqliteSessionTranscriptEvent({ agentId: "main", sessionId: "sqlite-import-session", @@ -572,10 +575,10 @@ describe("appendAssistantMessageToSessionTranscript", () => { it("mirrors a newly created scoped transcript header into SQLite", async () => { const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-transcript-state-")); vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); - const sessionFile = resolveSessionTranscriptPathInDir( - "sqlite-new-session", - fixture.sessionsDir(), - ); + const sessionFile = createSqliteSessionTranscriptLocator({ + agentId: "main", + sessionId: "sqlite-new-session", + }); const appended = await appendSessionTranscriptMessage({ transcriptPath: sessionFile, diff --git a/src/cron/isolated-agent/run.test-harness.ts b/src/cron/isolated-agent/run.test-harness.ts index 779cd47ef3d..9dc39e2e6d5 100644 --- a/src/cron/isolated-agent/run.test-harness.ts +++ b/src/cron/isolated-agent/run.test-harness.ts @@ -84,7 +84,7 @@ const normalizeVerboseLevelMock = createMock(); export const isThinkingLevelSupportedMock = createMock(); export const resolveSupportedThinkingLevelMock = createMock(); const supportsXHighThinkingMock = createMock(); -const resolveSessionTranscriptPathMock = createMock(); +const createSqliteSessionTranscriptLocatorMock = createMock(); const setSessionRuntimeModelMock = createMock(); const registerAgentRunContextMock = createMock(); const buildSafeExternalPromptMock = createMock(); @@ -117,7 +117,7 @@ vi.mock("./run.runtime.js", () => ({ isThinkingLevelSupported: isThinkingLevelSupportedMock, resolveSupportedThinkingLevel: resolveSupportedThinkingLevelMock, supportsXHighThinking: supportsXHighThinkingMock, - resolveSessionTranscriptPath: resolveSessionTranscriptPathMock, + createSqliteSessionTranscriptLocator: createSqliteSessionTranscriptLocatorMock, setSessionRuntimeModel: setSessionRuntimeModelMock, setCliSessionId: vi.fn(), logWarn: (...args: unknown[]) => logWarnMock(...args), @@ -174,7 +174,6 @@ vi.mock("./run-execution.runtime.js", () => ({ countActiveDescendantRuns: countActiveDescendantRunsMock, listDescendantRunsForRequester: listDescendantRunsForRequesterMock, normalizeVerboseLevel: normalizeVerboseLevelMock, - resolveSessionTranscriptPath: resolveSessionTranscriptPathMock, registerAgentRunContext: registerAgentRunContextMock, logWarn: (...args: unknown[]) => logWarnMock(...args), })); @@ -357,7 +356,10 @@ function resetRunExecutionMocks(): void { resolveFastModeStateMock.mockImplementation((params) => resolveFastModeStateImpl(params)); resolveCronAgentLaneMock.mockReturnValue(undefined); normalizeVerboseLevelMock.mockImplementation((value: unknown) => value ?? "off"); - resolveSessionTranscriptPathMock.mockReturnValue("/tmp/transcript.jsonl"); + createSqliteSessionTranscriptLocatorMock.mockImplementation( + ({ agentId = "main", sessionId }: { agentId?: string; sessionId: string }) => + `sqlite-transcript://${agentId}/${sessionId}.jsonl`, + ); registerAgentRunContextMock.mockReturnValue(undefined); runWithModelFallbackMock.mockReset(); runWithModelFallbackMock.mockResolvedValue(makeDefaultModelFallbackResult()); diff --git a/src/gateway/server.sessions-send.test.ts b/src/gateway/server.sessions-send.test.ts index f6a5835f999..b52df612965 100644 --- a/src/gateway/server.sessions-send.test.ts +++ b/src/gateway/server.sessions-send.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it, type Mock } from "vitest"; -import { resolveSessionTranscriptPath } from "../config/sessions/paths.js"; +import { createSqliteSessionTranscriptLocator } from "../config/sessions/paths.js"; import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js"; import { emitAgentEvent } from "../infra/agent-events.js"; import { captureEnv } from "../test-utils/env.js"; @@ -51,7 +51,7 @@ async function emitLifecycleAssistantReply(params: { }; const sessionId = commandParams.sessionId ?? params.defaultSessionId; const runId = commandParams.runId ?? sessionId; - const sessionFile = resolveSessionTranscriptPath(sessionId); + const sessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId }); const startedAt = Date.now(); emitAgentEvent({