

























@@ -6,6 +6,8 @@ type RepairReport = {
66repaired: boolean;
77droppedLines: number;
88rewrittenAssistantMessages?: number;
9+droppedBlankUserMessages?: number;
10+rewrittenUserMessages?: number;
911backupPath?: string;
1012reason?: string;
1113};
@@ -21,7 +23,7 @@ type RepairReport = {
21232224type SessionMessageEntry = {
2325type: "message";
24-message: { role: "assistant"; content: unknown[] } & Record<string, unknown>;
26+message: { role: string; content?: unknown } & Record<string, unknown>;
2527} & Record<string, unknown>;
26282729function isSessionHeader(entry: unknown): entry is { type: string; id: string } {
@@ -69,13 +71,71 @@ function rewriteAssistantEntryWithEmptyContent(entry: SessionMessageEntry): Sess
6971};
7072}
717372-function buildRepairSummaryParts(droppedLines: number, rewrittenAssistantMessages: number): string {
74+type UserEntryRepair =
75+| { kind: "drop" }
76+| { kind: "rewrite"; entry: SessionMessageEntry }
77+| { kind: "keep" };
78+79+function repairUserEntryWithBlankTextContent(entry: SessionMessageEntry): UserEntryRepair {
80+const content = entry.message.content;
81+if (typeof content === "string") {
82+return content.trim() ? { kind: "keep" } : { kind: "drop" };
83+}
84+if (!Array.isArray(content)) {
85+return { kind: "keep" };
86+}
87+88+let touched = false;
89+const nextContent = content.filter((block) => {
90+if (!block || typeof block !== "object") {
91+return true;
92+}
93+if ((block as { type?: unknown }).type !== "text") {
94+return true;
95+}
96+const text = (block as { text?: unknown }).text;
97+if (typeof text !== "string" || text.trim().length > 0) {
98+return true;
99+}
100+touched = true;
101+return false;
102+});
103+if (nextContent.length === 0) {
104+return { kind: "drop" };
105+}
106+if (!touched) {
107+return { kind: "keep" };
108+}
109+return {
110+kind: "rewrite",
111+entry: {
112+ ...entry,
113+message: {
114+ ...entry.message,
115+content: nextContent,
116+},
117+},
118+};
119+}
120+121+function buildRepairSummaryParts(params: {
122+droppedLines: number;
123+rewrittenAssistantMessages: number;
124+droppedBlankUserMessages: number;
125+rewrittenUserMessages: number;
126+}): string {
73127const parts: string[] = [];
74-if (droppedLines > 0) {
75-parts.push(`dropped ${droppedLines} malformed line(s)`);
128+if (params.droppedLines > 0) {
129+parts.push(`dropped ${params.droppedLines} malformed line(s)`);
76130}
77-if (rewrittenAssistantMessages > 0) {
78-parts.push(`rewrote ${rewrittenAssistantMessages} assistant message(s)`);
131+if (params.rewrittenAssistantMessages > 0) {
132+parts.push(`rewrote ${params.rewrittenAssistantMessages} assistant message(s)`);
133+}
134+if (params.droppedBlankUserMessages > 0) {
135+parts.push(`dropped ${params.droppedBlankUserMessages} blank user message(s)`);
136+}
137+if (params.rewrittenUserMessages > 0) {
138+parts.push(`rewrote ${params.rewrittenUserMessages} user message(s)`);
79139}
80140// Caller only invokes this once at least one counter is non-zero, so the
81141// empty-array branch is unreachable in production. Kept for defensive output.
@@ -108,6 +168,8 @@ export async function repairSessionFileIfNeeded(params: {
108168const entries: unknown[] = [];
109169let droppedLines = 0;
110170let rewrittenAssistantMessages = 0;
171+let droppedBlankUserMessages = 0;
172+let rewrittenUserMessages = 0;
111173112174for (const line of lines) {
113175if (!line.trim()) {
@@ -120,6 +182,24 @@ export async function repairSessionFileIfNeeded(params: {
120182rewrittenAssistantMessages += 1;
121183continue;
122184}
185+if (
186+entry &&
187+typeof entry === "object" &&
188+(entry as { type?: unknown }).type === "message" &&
189+typeof (entry as { message?: unknown }).message === "object" &&
190+((entry as { message: { role?: unknown } }).message?.role ?? undefined) === "user"
191+) {
192+const repairedUser = repairUserEntryWithBlankTextContent(entry as SessionMessageEntry);
193+if (repairedUser.kind === "drop") {
194+droppedBlankUserMessages += 1;
195+continue;
196+}
197+if (repairedUser.kind === "rewrite") {
198+entries.push(repairedUser.entry);
199+rewrittenUserMessages += 1;
200+continue;
201+}
202+}
123203entries.push(entry);
124204} catch {
125205droppedLines += 1;
@@ -137,7 +217,12 @@ export async function repairSessionFileIfNeeded(params: {
137217return { repaired: false, droppedLines, reason: "invalid session header" };
138218}
139219140-if (droppedLines === 0 && rewrittenAssistantMessages === 0) {
220+if (
221+droppedLines === 0 &&
222+rewrittenAssistantMessages === 0 &&
223+droppedBlankUserMessages === 0 &&
224+rewrittenUserMessages === 0
225+) {
141226return { repaired: false, droppedLines: 0 };
142227}
143228@@ -169,15 +254,26 @@ export async function repairSessionFileIfNeeded(params: {
169254repaired: false,
170255 droppedLines,
171256 rewrittenAssistantMessages,
257+ droppedBlankUserMessages,
258+ rewrittenUserMessages,
172259reason: `repair failed: ${err instanceof Error ? err.message : "unknown error"}`,
173260};
174261}
175262176263params.warn?.(
177-`session file repaired: ${buildRepairSummaryParts(
264+`session file repaired: ${buildRepairSummaryParts({
178265 droppedLines,
179266 rewrittenAssistantMessages,
180- )} (${path.basename(sessionFile)})`,
267+ droppedBlankUserMessages,
268+ rewrittenUserMessages,
269+ })} (${path.basename(sessionFile)})`,
181270);
182-return { repaired: true, droppedLines, rewrittenAssistantMessages, backupPath };
271+return {
272+repaired: true,
273+ droppedLines,
274+ rewrittenAssistantMessages,
275+ droppedBlankUserMessages,
276+ rewrittenUserMessages,
277+ backupPath,
278+};
183279}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。