mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(agents): preserve delivered assistant replies in session repair
This commit is contained in:
@@ -117,7 +117,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
errorMessage: "transient stream failure",
|
||||
},
|
||||
};
|
||||
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
|
||||
// Follow-up keeps this case focused on empty error-turn repair.
|
||||
const followUp = {
|
||||
type: "message",
|
||||
id: "msg-3",
|
||||
@@ -172,6 +172,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
|
||||
expect(result.repaired).toBe(true);
|
||||
expect(result.rewrittenUserMessages).toBe(1);
|
||||
expect(result.droppedBlankUserMessages).toBe(0);
|
||||
expect(debug.mock.calls[0]?.[0]).toContain("rewrote 1 user message(s)");
|
||||
|
||||
const repaired = await fs.readFile(file, "utf-8");
|
||||
@@ -292,7 +293,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
stopReason: "stop",
|
||||
},
|
||||
};
|
||||
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
|
||||
// Follow-up keeps this case focused on silent-reply preservation.
|
||||
const followUp = {
|
||||
type: "message",
|
||||
id: "msg-3",
|
||||
@@ -507,7 +508,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
stopReason: "error",
|
||||
},
|
||||
};
|
||||
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
|
||||
// Follow-up keeps this case focused on idempotent empty error-turn repair.
|
||||
const followUp = {
|
||||
type: "message",
|
||||
id: "msg-3",
|
||||
|
||||
@@ -10,6 +10,7 @@ type RepairReport = {
|
||||
repaired: boolean;
|
||||
droppedLines: number;
|
||||
rewrittenAssistantMessages?: number;
|
||||
droppedBlankUserMessages?: number;
|
||||
rewrittenUserMessages?: number;
|
||||
backupPath?: string;
|
||||
reason?: string;
|
||||
@@ -66,7 +67,10 @@ function rewriteAssistantEntryWithEmptyContent(entry: SessionMessageEntry): Sess
|
||||
};
|
||||
}
|
||||
|
||||
type UserEntryRepair = { kind: "rewrite"; entry: SessionMessageEntry } | { kind: "keep" };
|
||||
type UserEntryRepair =
|
||||
| { kind: "drop" }
|
||||
| { kind: "rewrite"; entry: SessionMessageEntry }
|
||||
| { kind: "keep" };
|
||||
|
||||
function repairUserEntryWithBlankTextContent(entry: SessionMessageEntry): UserEntryRepair {
|
||||
const content = entry.message.content;
|
||||
@@ -134,6 +138,7 @@ function repairUserEntryWithBlankTextContent(entry: SessionMessageEntry): UserEn
|
||||
function buildRepairSummaryParts(params: {
|
||||
droppedLines: number;
|
||||
rewrittenAssistantMessages: number;
|
||||
droppedBlankUserMessages: number;
|
||||
rewrittenUserMessages: number;
|
||||
}): string {
|
||||
const parts: string[] = [];
|
||||
@@ -143,6 +148,9 @@ function buildRepairSummaryParts(params: {
|
||||
if (params.rewrittenAssistantMessages > 0) {
|
||||
parts.push(`rewrote ${params.rewrittenAssistantMessages} assistant message(s)`);
|
||||
}
|
||||
if (params.droppedBlankUserMessages > 0) {
|
||||
parts.push(`dropped ${params.droppedBlankUserMessages} blank user message(s)`);
|
||||
}
|
||||
if (params.rewrittenUserMessages > 0) {
|
||||
parts.push(`rewrote ${params.rewrittenUserMessages} user message(s)`);
|
||||
}
|
||||
@@ -176,6 +184,7 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
const entries: unknown[] = [];
|
||||
let droppedLines = 0;
|
||||
let rewrittenAssistantMessages = 0;
|
||||
let droppedBlankUserMessages = 0;
|
||||
let rewrittenUserMessages = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
@@ -197,6 +206,10 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
((entry as { message: { role?: unknown } }).message?.role ?? undefined) === "user"
|
||||
) {
|
||||
const repairedUser = repairUserEntryWithBlankTextContent(entry as SessionMessageEntry);
|
||||
if (repairedUser.kind === "drop") {
|
||||
droppedBlankUserMessages += 1;
|
||||
continue;
|
||||
}
|
||||
if (repairedUser.kind === "rewrite") {
|
||||
entries.push(repairedUser.entry);
|
||||
rewrittenUserMessages += 1;
|
||||
@@ -220,7 +233,12 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
return { repaired: false, droppedLines, reason: "invalid session header" };
|
||||
}
|
||||
|
||||
if (droppedLines === 0 && rewrittenAssistantMessages === 0 && rewrittenUserMessages === 0) {
|
||||
if (
|
||||
droppedLines === 0 &&
|
||||
rewrittenAssistantMessages === 0 &&
|
||||
droppedBlankUserMessages === 0 &&
|
||||
rewrittenUserMessages === 0
|
||||
) {
|
||||
return { repaired: false, droppedLines: 0 };
|
||||
}
|
||||
|
||||
@@ -252,6 +270,7 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
repaired: false,
|
||||
droppedLines,
|
||||
rewrittenAssistantMessages,
|
||||
droppedBlankUserMessages,
|
||||
rewrittenUserMessages,
|
||||
reason: `repair failed: ${err instanceof Error ? err.message : "unknown error"}`,
|
||||
};
|
||||
@@ -261,6 +280,7 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
`session file repaired: ${buildRepairSummaryParts({
|
||||
droppedLines,
|
||||
rewrittenAssistantMessages,
|
||||
droppedBlankUserMessages,
|
||||
rewrittenUserMessages,
|
||||
})} (${path.basename(sessionFile)})`,
|
||||
);
|
||||
@@ -268,6 +288,7 @@ export async function repairSessionFileIfNeeded(params: {
|
||||
repaired: true,
|
||||
droppedLines,
|
||||
rewrittenAssistantMessages,
|
||||
droppedBlankUserMessages,
|
||||
rewrittenUserMessages,
|
||||
backupPath,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user