mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
fix(gateway): read active transcript history branch
This commit is contained in:
@@ -501,6 +501,74 @@ describe("readSessionMessages", () => {
|
||||
expect(typeof marker.timestamp).toBe("number");
|
||||
});
|
||||
|
||||
test("reads only the active branch when transcript rewrites abandon older entries", () => {
|
||||
const sessionId = "test-session-active-branch";
|
||||
const sessionFile = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
{
|
||||
type: "session",
|
||||
version: 3,
|
||||
id: sessionId,
|
||||
cwd: tmpDir,
|
||||
timestamp: "2026-04-27T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
id: "original",
|
||||
parentId: null,
|
||||
timestamp: "2026-04-27T00:00:01.000Z",
|
||||
message: {
|
||||
role: "user",
|
||||
content: "Sender (untrusted metadata): webchat\n\noriginal wrapped prompt",
|
||||
timestamp: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
id: "clean",
|
||||
parentId: null,
|
||||
timestamp: "2026-04-27T00:00:02.000Z",
|
||||
message: { role: "user", content: "clean prompt", timestamp: 2 },
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
id: "answer",
|
||||
parentId: "clean",
|
||||
timestamp: "2026-04-27T00:00:03.000Z",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "clean answer" }],
|
||||
api: "chat",
|
||||
provider: "openclaw",
|
||||
model: "test",
|
||||
usage: {},
|
||||
stopReason: "stop",
|
||||
timestamp: 3,
|
||||
},
|
||||
},
|
||||
];
|
||||
fs.writeFileSync(sessionFile, lines.map((line) => JSON.stringify(line)).join("\n"), "utf-8");
|
||||
const rawTranscript = fs.readFileSync(sessionFile, "utf-8");
|
||||
expect(rawTranscript).toContain("original wrapped prompt");
|
||||
expect(rawTranscript).toContain("clean prompt");
|
||||
|
||||
const out = readSessionMessages(sessionId, storePath, sessionFile);
|
||||
expect(out).toHaveLength(2);
|
||||
expect(out).toEqual([
|
||||
expect.objectContaining({
|
||||
role: "user",
|
||||
content: "clean prompt",
|
||||
__openclaw: expect.objectContaining({ seq: 1 }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "clean answer" }],
|
||||
__openclaw: expect.objectContaining({ seq: 2 }),
|
||||
}),
|
||||
]);
|
||||
expect(JSON.stringify(out)).not.toContain("original wrapped prompt");
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
sessionId: "cross-agent-default-root",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import { SessionManager, type SessionEntry } from "@mariozechner/pi-coding-agent";
|
||||
import { deriveSessionTotalTokens, hasNonzeroUsage, normalizeUsage } from "../agents/usage.js";
|
||||
import { jsonUtf8Bytes } from "../infra/json-utf8-bytes.js";
|
||||
import { hasInterSessionUserProvenance } from "../sessions/input-provenance.js";
|
||||
@@ -103,6 +104,60 @@ export function readSessionMessages(
|
||||
}
|
||||
|
||||
const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
||||
const hasTreeEntries = lines.some((line) => {
|
||||
if (!line.trim()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(line) as { type?: unknown; id?: unknown; parentId?: unknown };
|
||||
return parsed.type !== "session" && typeof parsed.id === "string" && "parentId" in parsed;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
let branchEntries: SessionEntry[] | null = null;
|
||||
if (hasTreeEntries) {
|
||||
try {
|
||||
branchEntries = SessionManager.open(filePath).getBranch();
|
||||
} catch {
|
||||
branchEntries = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (branchEntries) {
|
||||
const messages: unknown[] = [];
|
||||
let messageSeq = 0;
|
||||
for (const entry of branchEntries) {
|
||||
if (entry.type === "message" && entry.message) {
|
||||
messageSeq += 1;
|
||||
messages.push(
|
||||
attachOpenClawTranscriptMeta(entry.message, {
|
||||
...(typeof entry.id === "string" ? { id: entry.id } : {}),
|
||||
seq: messageSeq,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.type === "compaction") {
|
||||
const ts = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : Number.NaN;
|
||||
const timestamp = Number.isFinite(ts) ? ts : Date.now();
|
||||
messageSeq += 1;
|
||||
messages.push({
|
||||
role: "system",
|
||||
content: [{ type: "text", text: "Compaction" }],
|
||||
timestamp,
|
||||
__openclaw: {
|
||||
kind: "compaction",
|
||||
id: typeof entry.id === "string" ? entry.id : undefined,
|
||||
seq: messageSeq,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
const messages: unknown[] = [];
|
||||
let messageSeq = 0;
|
||||
for (const line of lines) {
|
||||
|
||||
Reference in New Issue
Block a user