





























@@ -580,4 +580,123 @@ describe("repairSessionFileIfNeeded", () => {
580580const after = await fs.readFile(file, "utf-8");
581581expect(after).toBe(original);
582582});
583+584+it("drops type:message entries with null role instead of preserving them through repair (#77228)", async () => {
585+const { file } = await createTempSessionPath();
586+const { header, message } = buildSessionHeaderAndMessage();
587+588+const nullRoleEntry = {
589+type: "message",
590+id: "corrupt-1",
591+parentId: null,
592+timestamp: new Date().toISOString(),
593+message: { role: null, content: "ignored" },
594+};
595+const missingRoleEntry = {
596+type: "message",
597+id: "corrupt-2",
598+parentId: null,
599+timestamp: new Date().toISOString(),
600+message: { content: "no role at all" },
601+};
602+const emptyRoleEntry = {
603+type: "message",
604+id: "corrupt-3",
605+parentId: null,
606+timestamp: new Date().toISOString(),
607+message: { role: " ", content: "blank role" },
608+};
609+610+const content = [
611+JSON.stringify(header),
612+JSON.stringify(message),
613+JSON.stringify(nullRoleEntry),
614+JSON.stringify(missingRoleEntry),
615+JSON.stringify(emptyRoleEntry),
616+].join("\n");
617+await fs.writeFile(file, `${content}\n`, "utf-8");
618+619+const result = await repairSessionFileIfNeeded({ sessionFile: file });
620+621+expect(result.repaired).toBe(true);
622+expect(result.droppedLines).toBe(3);
623+expect(result.backupPath).toBeTruthy();
624+625+const after = await fs.readFile(file, "utf-8");
626+const lines = after.trimEnd().split("\n");
627+expect(lines).toHaveLength(2);
628+expect(JSON.parse(lines[0])).toEqual(header);
629+expect(JSON.parse(lines[1])).toEqual(message);
630+expect(after).not.toContain('"role":null');
631+});
632+633+it("drops a type:message entry whose message field is missing or non-object", async () => {
634+const { file } = await createTempSessionPath();
635+const { header, message } = buildSessionHeaderAndMessage();
636+637+const missingMessage = {
638+type: "message",
639+id: "corrupt-4",
640+parentId: null,
641+timestamp: new Date().toISOString(),
642+};
643+const stringMessage = {
644+type: "message",
645+id: "corrupt-5",
646+parentId: null,
647+timestamp: new Date().toISOString(),
648+message: "not an object",
649+};
650+651+const content = [
652+JSON.stringify(header),
653+JSON.stringify(message),
654+JSON.stringify(missingMessage),
655+JSON.stringify(stringMessage),
656+].join("\n");
657+await fs.writeFile(file, `${content}\n`, "utf-8");
658+659+const result = await repairSessionFileIfNeeded({ sessionFile: file });
660+661+expect(result.repaired).toBe(true);
662+expect(result.droppedLines).toBe(2);
663+664+const after = await fs.readFile(file, "utf-8");
665+const lines = after.trimEnd().split("\n");
666+expect(lines).toHaveLength(2);
667+});
668+669+it("preserves non-`message` envelope types (e.g. compactionSummary, custom) without role inspection", async () => {
670+const { file } = await createTempSessionPath();
671+const { header, message } = buildSessionHeaderAndMessage();
672+673+const summary = {
674+type: "summary",
675+id: "summary-1",
676+timestamp: new Date().toISOString(),
677+summary: "opaque summary blob",
678+};
679+const custom = {
680+type: "custom",
681+id: "custom-1",
682+customType: "model-snapshot",
683+timestamp: new Date().toISOString(),
684+data: { provider: "openai", modelApi: "openai-responses", modelId: "gpt-5" },
685+};
686+687+const content = [
688+JSON.stringify(header),
689+JSON.stringify(message),
690+JSON.stringify(summary),
691+JSON.stringify(custom),
692+].join("\n");
693+await fs.writeFile(file, `${content}\n`, "utf-8");
694+695+const result = await repairSessionFileIfNeeded({ sessionFile: file });
696+697+expect(result.repaired).toBe(false);
698+expect(result.droppedLines).toBe(0);
699+const after = await fs.readFile(file, "utf-8");
700+expect(after).toBe(`${content}\n`);
701+});
583702});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。