Skip to content

Commit c58d2aa

Browse files
committed
Sessions: fix sessions_list transcriptPath path resolution
1 parent f57b466 commit c58d2aa

File tree

5 files changed

+59
-7
lines changed

5 files changed

+59
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Docs: https://docs.openclaw.ai
8282
- Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz.
8383
- TUI/Session model status: clear stale runtime model identity when model overrides change so `/model` updates are reflected immediately in `sessions.patch` responses and `sessions.list` status surfaces. (#28619) Thanks @lejean2000.
8484
- Memory/Hybrid recall: when strict hybrid scoring yields no hits, preserve keyword-backed matches using a text-weight floor so freshly indexed lexical canaries no longer disappear behind `minScore` filtering. (#29112) Thanks @ceo-nada.
85+
- Agents/Sessions list transcript paths: resolve `sessions_list` `transcriptPath` via agent-aware session path options and ignore combined-store sentinel paths (`(multiple)`) so listed transcript paths always point to the state directory. (#28379) Thanks @fafuzuoluo.
8586
- Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc.
8687
- Browser/Navigate: resolve the correct `targetId` in navigate responses after renderer swaps. (#25326) Thanks @stone-jin and @vincentkoc.
8788
- Agents/Ollama discovery: skip Ollama discovery when explicit models are configured. (#28827) Thanks @Kansodata and @vincentkoc.

src/agents/openclaw-tools.sessions.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from "node:path";
12
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
23
import {
34
addSubagentRunForTests,
@@ -171,6 +172,46 @@ describe("sessions tools", () => {
171172
expect(cronDetails.sessions?.[0]?.kind).toBe("cron");
172173
});
173174

175+
it("sessions_list resolves transcriptPath from agent state dir for multi-store listings", async () => {
176+
callGatewayMock.mockImplementation(async (opts: unknown) => {
177+
const request = opts as { method?: string };
178+
if (request.method === "sessions.list") {
179+
return {
180+
path: "(multiple)",
181+
sessions: [
182+
{
183+
key: "main",
184+
kind: "direct",
185+
sessionId: "sess-main",
186+
updatedAt: 12,
187+
},
188+
],
189+
};
190+
}
191+
return {};
192+
});
193+
194+
const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_list");
195+
expect(tool).toBeDefined();
196+
if (!tool) {
197+
throw new Error("missing sessions_list tool");
198+
}
199+
200+
const result = await tool.execute("call2b", {});
201+
const details = result.details as {
202+
sessions?: Array<{
203+
key?: string;
204+
transcriptPath?: string;
205+
}>;
206+
};
207+
const main = details.sessions?.find((session) => session.key === "main");
208+
expect(typeof main?.transcriptPath).toBe("string");
209+
expect(main?.transcriptPath).not.toContain("(multiple)");
210+
expect(main?.transcriptPath).toContain(
211+
path.join("agents", "main", "sessions", "sess-main.jsonl"),
212+
);
213+
});
214+
174215
it("sessions_history filters tool messages by default", async () => {
175216
callGatewayMock.mockImplementation(async (opts: unknown) => {
176217
const request = opts as { method?: string };

src/agents/tools/sessions-list-tool.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import path from "node:path";
21
import { Type } from "@sinclair/typebox";
32
import { loadConfig } from "../../config/config.js";
4-
import { resolveSessionFilePath } from "../../config/sessions.js";
3+
import { resolveSessionFilePath, resolveSessionFilePathOptions } from "../../config/sessions.js";
54
import { callGateway } from "../../gateway/call.js";
65
import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
76
import type { AnyAgentTool } from "./common.js";
@@ -156,13 +155,14 @@ export function createSessionsListTool(opts?: {
156155
let transcriptPath: string | undefined;
157156
if (sessionId && storePath) {
158157
try {
158+
const sessionPathOpts = resolveSessionFilePathOptions({
159+
agentId: resolveAgentIdFromSessionKey(key),
160+
storePath,
161+
});
159162
transcriptPath = resolveSessionFilePath(
160163
sessionId,
161164
sessionFile ? { sessionFile } : undefined,
162-
{
163-
agentId: resolveAgentIdFromSessionKey(key),
164-
sessionsDir: path.dirname(storePath),
165-
},
165+
sessionPathOpts,
166166
);
167167
} catch {
168168
transcriptPath = undefined;

src/config/sessions/paths.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ export type SessionFilePathOptions = {
3939
sessionsDir?: string;
4040
};
4141

42+
const MULTI_STORE_PATH_SENTINEL = "(multiple)";
43+
4244
export function resolveSessionFilePathOptions(params: {
4345
agentId?: string;
4446
storePath?: string;
4547
}): SessionFilePathOptions | undefined {
4648
const agentId = params.agentId?.trim();
4749
const storePath = params.storePath?.trim();
48-
if (storePath) {
50+
if (storePath && storePath !== MULTI_STORE_PATH_SENTINEL) {
4951
const sessionsDir = path.dirname(path.resolve(storePath));
5052
return agentId ? { sessionsDir, agentId } : { sessionsDir };
5153
}

src/config/sessions/sessions.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import type { SessionConfig } from "../types.base.js";
1414
import {
1515
resolveSessionFilePath,
16+
resolveSessionFilePathOptions,
1617
resolveSessionTranscriptPathInDir,
1718
validateSessionId,
1819
} from "./paths.js";
@@ -68,6 +69,13 @@ describe("session path safety", () => {
6869
expect(resolved).toBe(path.resolve(sessionsDir, "sess-1.jsonl"));
6970
});
7071

72+
it("ignores multi-store sentinel paths when deriving session file options", () => {
73+
expect(resolveSessionFilePathOptions({ agentId: "worker", storePath: "(multiple)" })).toEqual({
74+
agentId: "worker",
75+
});
76+
expect(resolveSessionFilePathOptions({ storePath: "(multiple)" })).toBeUndefined();
77+
});
78+
7179
it("accepts symlink-alias session paths that resolve under the sessions dir", () => {
7280
if (process.platform === "win32") {
7381
return;

0 commit comments

Comments
 (0)