Skip to content

Commit 66c187f

Browse files
fix(agents): preserve delivered assistant replies in session repair
1 parent 9ca1677 commit 66c187f

2 files changed

Lines changed: 27 additions & 5 deletions

File tree

src/agents/session-file-repair.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ describe("repairSessionFileIfNeeded", () => {
117117
errorMessage: "transient stream failure",
118118
},
119119
};
120-
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
120+
// Follow-up keeps this case focused on empty error-turn repair.
121121
const followUp = {
122122
type: "message",
123123
id: "msg-3",
@@ -172,6 +172,7 @@ describe("repairSessionFileIfNeeded", () => {
172172

173173
expect(result.repaired).toBe(true);
174174
expect(result.rewrittenUserMessages).toBe(1);
175+
expect(result.droppedBlankUserMessages).toBe(0);
175176
expect(debug.mock.calls[0]?.[0]).toContain("rewrote 1 user message(s)");
176177

177178
const repaired = await fs.readFile(file, "utf-8");
@@ -292,7 +293,7 @@ describe("repairSessionFileIfNeeded", () => {
292293
stopReason: "stop",
293294
},
294295
};
295-
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
296+
// Follow-up keeps this case focused on silent-reply preservation.
296297
const followUp = {
297298
type: "message",
298299
id: "msg-3",
@@ -507,7 +508,7 @@ describe("repairSessionFileIfNeeded", () => {
507508
stopReason: "error",
508509
},
509510
};
510-
// Follow-up so the session doesn't end on assistant (trailing-trim is tested separately).
511+
// Follow-up keeps this case focused on idempotent empty error-turn repair.
511512
const followUp = {
512513
type: "message",
513514
id: "msg-3",

src/agents/session-file-repair.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type RepairReport = {
1010
repaired: boolean;
1111
droppedLines: number;
1212
rewrittenAssistantMessages?: number;
13+
droppedBlankUserMessages?: number;
1314
rewrittenUserMessages?: number;
1415
backupPath?: string;
1516
reason?: string;
@@ -66,7 +67,10 @@ function rewriteAssistantEntryWithEmptyContent(entry: SessionMessageEntry): Sess
6667
};
6768
}
6869

69-
type UserEntryRepair = { kind: "rewrite"; entry: SessionMessageEntry } | { kind: "keep" };
70+
type UserEntryRepair =
71+
| { kind: "drop" }
72+
| { kind: "rewrite"; entry: SessionMessageEntry }
73+
| { kind: "keep" };
7074

7175
function repairUserEntryWithBlankTextContent(entry: SessionMessageEntry): UserEntryRepair {
7276
const content = entry.message.content;
@@ -134,6 +138,7 @@ function repairUserEntryWithBlankTextContent(entry: SessionMessageEntry): UserEn
134138
function buildRepairSummaryParts(params: {
135139
droppedLines: number;
136140
rewrittenAssistantMessages: number;
141+
droppedBlankUserMessages: number;
137142
rewrittenUserMessages: number;
138143
}): string {
139144
const parts: string[] = [];
@@ -143,6 +148,9 @@ function buildRepairSummaryParts(params: {
143148
if (params.rewrittenAssistantMessages > 0) {
144149
parts.push(`rewrote ${params.rewrittenAssistantMessages} assistant message(s)`);
145150
}
151+
if (params.droppedBlankUserMessages > 0) {
152+
parts.push(`dropped ${params.droppedBlankUserMessages} blank user message(s)`);
153+
}
146154
if (params.rewrittenUserMessages > 0) {
147155
parts.push(`rewrote ${params.rewrittenUserMessages} user message(s)`);
148156
}
@@ -176,6 +184,7 @@ export async function repairSessionFileIfNeeded(params: {
176184
const entries: unknown[] = [];
177185
let droppedLines = 0;
178186
let rewrittenAssistantMessages = 0;
187+
let droppedBlankUserMessages = 0;
179188
let rewrittenUserMessages = 0;
180189

181190
for (const line of lines) {
@@ -197,6 +206,10 @@ export async function repairSessionFileIfNeeded(params: {
197206
((entry as { message: { role?: unknown } }).message?.role ?? undefined) === "user"
198207
) {
199208
const repairedUser = repairUserEntryWithBlankTextContent(entry as SessionMessageEntry);
209+
if (repairedUser.kind === "drop") {
210+
droppedBlankUserMessages += 1;
211+
continue;
212+
}
200213
if (repairedUser.kind === "rewrite") {
201214
entries.push(repairedUser.entry);
202215
rewrittenUserMessages += 1;
@@ -220,7 +233,12 @@ export async function repairSessionFileIfNeeded(params: {
220233
return { repaired: false, droppedLines, reason: "invalid session header" };
221234
}
222235

223-
if (droppedLines === 0 && rewrittenAssistantMessages === 0 && rewrittenUserMessages === 0) {
236+
if (
237+
droppedLines === 0 &&
238+
rewrittenAssistantMessages === 0 &&
239+
droppedBlankUserMessages === 0 &&
240+
rewrittenUserMessages === 0
241+
) {
224242
return { repaired: false, droppedLines: 0 };
225243
}
226244

@@ -252,6 +270,7 @@ export async function repairSessionFileIfNeeded(params: {
252270
repaired: false,
253271
droppedLines,
254272
rewrittenAssistantMessages,
273+
droppedBlankUserMessages,
255274
rewrittenUserMessages,
256275
reason: `repair failed: ${err instanceof Error ? err.message : "unknown error"}`,
257276
};
@@ -261,13 +280,15 @@ export async function repairSessionFileIfNeeded(params: {
261280
`session file repaired: ${buildRepairSummaryParts({
262281
droppedLines,
263282
rewrittenAssistantMessages,
283+
droppedBlankUserMessages,
264284
rewrittenUserMessages,
265285
})} (${path.basename(sessionFile)})`,
266286
);
267287
return {
268288
repaired: true,
269289
droppedLines,
270290
rewrittenAssistantMessages,
291+
droppedBlankUserMessages,
271292
rewrittenUserMessages,
272293
backupPath,
273294
};

0 commit comments

Comments
 (0)