clawdbot-291: loosen transcript read scope

This commit is contained in:
Josh Lehman
2026-06-01 06:33:22 -07:00
parent 1c13f20254
commit 7e98d03e88
2 changed files with 81 additions and 4 deletions

View File

@@ -232,6 +232,48 @@ describe("session accessor file-backed seam", () => {
expect(fs.statSync(transcriptPath).mode & 0o777).toBe(0o600);
});
it("loads transcript events without a session key when the read target is explicit", async () => {
const scope = {
sessionFile: transcriptPath,
sessionId: "session-1",
};
const event = {
id: "msg-1",
message: { role: "user", content: "hello" },
parentId: null,
type: "message",
};
await appendTranscriptEvent(
{
...scope,
sessionKey: "agent:main:main",
storePath,
},
event,
);
await expect(loadTranscriptEvents(scope)).resolves.toEqual([event]);
});
it("loads transcript events from a generated read target without a session key", async () => {
const event = {
id: "msg-1",
message: { role: "user", content: "hello" },
parentId: null,
type: "message",
};
fs.writeFileSync(path.join(tempDir, "session-1.jsonl"), `${JSON.stringify(event)}\n`, "utf-8");
await expect(
loadTranscriptEvents({
sessionId: "session-1",
storePath,
}),
).resolves.toEqual([event]);
});
it("appends messages and publishes updates through a session scope", async () => {
const scope = {
agentId: "main",

View File

@@ -5,7 +5,11 @@ import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js
import type { SessionTranscriptUpdate } from "../../sessions/transcript-events.js";
import { getRuntimeConfig } from "../io.js";
import type { OpenClawConfig } from "../types.openclaw.js";
import { resolveSessionTranscriptPathInDir, resolveStorePath } from "./paths.js";
import {
resolveSessionTranscriptPath,
resolveSessionTranscriptPathInDir,
resolveStorePath,
} from "./paths.js";
import { resolveAndPersistSessionFile } from "./session-file.js";
import {
getSessionEntry,
@@ -34,12 +38,17 @@ export type SessionAccessScope = {
storePath?: string;
};
export type SessionTranscriptAccessScope = SessionAccessScope & {
export type SessionTranscriptReadScope = Omit<SessionAccessScope, "sessionKey"> & {
sessionFile?: string;
sessionId: string;
sessionKey?: string;
threadId?: string | number;
};
export type SessionTranscriptAccessScope = SessionTranscriptReadScope & {
sessionKey: string;
};
export type SessionTranscriptWriteScope = Omit<SessionTranscriptAccessScope, "sessionId"> & {
sessionId?: string;
};
@@ -182,9 +191,9 @@ export async function updateSessionEntry(
/** Loads raw transcript events through the storage-neutral accessor seam. */
export async function loadTranscriptEvents(
scope: SessionTranscriptAccessScope,
scope: SessionTranscriptReadScope,
): Promise<TranscriptEvent[]> {
const transcript = await resolveTranscriptAccess(scope);
const transcript = await resolveTranscriptReadAccess(scope);
const events: TranscriptEvent[] = [];
for await (const line of streamSessionTranscriptLines(transcript.sessionFile)) {
events.push(JSON.parse(line) as TranscriptEvent);
@@ -269,6 +278,32 @@ function resolveAccessStorePath(scope: SessionAccessScope): string {
});
}
async function resolveTranscriptReadAccess(scope: SessionTranscriptReadScope): Promise<{
sessionFile: string;
}> {
if (scope.sessionFile?.trim()) {
return { sessionFile: scope.sessionFile };
}
if (scope.sessionKey) {
return await resolveTranscriptAccess({ ...scope, sessionKey: scope.sessionKey });
}
if (scope.storePath) {
return {
sessionFile: resolveSessionTranscriptPathInDir(
scope.sessionId,
path.dirname(path.resolve(scope.storePath)),
scope.threadId,
),
};
}
if (scope.agentId) {
return {
sessionFile: resolveSessionTranscriptPath(scope.sessionId, scope.agentId, scope.threadId),
};
}
throw new Error(`Cannot resolve transcript read scope without a session target`);
}
async function resolveTranscriptAccess(scope: SessionTranscriptWriteScope): Promise<{
sessionFile: string;
}> {