
























@@ -113,6 +113,38 @@ function buildBasicSessionTranscript(
113113];
114114}
115115116+function requireRecord(value: unknown, label: string): Record<string, unknown> {
117+expect(value, label).toBeTypeOf("object");
118+expect(value, label).not.toBeNull();
119+return value as Record<string, unknown>;
120+}
121+122+function expectMessageFields(
123+message: unknown,
124+fields: { role?: string; content?: unknown; openclaw?: Record<string, unknown> },
125+) {
126+const record = requireRecord(message, "message");
127+if ("role" in fields) {
128+expect(record.role).toBe(fields.role);
129+}
130+if ("content" in fields) {
131+expect(record.content).toEqual(fields.content);
132+}
133+if (fields.openclaw) {
134+const metadata = requireRecord(record.__openclaw, "message metadata");
135+for (const [key, value] of Object.entries(fields.openclaw)) {
136+expect(metadata[key]).toEqual(value);
137+}
138+}
139+}
140+141+function expectUsageFields(usage: unknown, fields: Record<string, unknown>) {
142+const record = requireRecord(usage, "usage");
143+for (const [key, value] of Object.entries(fields)) {
144+expect(record[key]).toEqual(value);
145+}
146+}
147+116148describe("readFirstUserMessageFromTranscript", () => {
117149let tmpDir: string;
118150let storePath: string;
@@ -598,18 +630,9 @@ describe("readSessionMessages", () => {
598630maxBytes: 1024,
599631});
600632601-expect(out).toEqual([
602-expect.objectContaining({
603-role: "user",
604-content: "recent",
605-__openclaw: expect.objectContaining({ seq: 3 }),
606-}),
607-expect.objectContaining({
608-role: "assistant",
609-content: "latest",
610-__openclaw: expect.objectContaining({ seq: 4 }),
611-}),
612-]);
633+expect(out).toHaveLength(2);
634+expectMessageFields(out[0], { role: "user", content: "recent", openclaw: { seq: 3 } });
635+expectMessageFields(out[1], { role: "assistant", content: "latest", openclaw: { seq: 4 } });
613636});
614637615638test("bounds recent-message reads for large append-only transcripts", () => {
@@ -636,7 +659,7 @@ describe("readSessionMessages", () => {
636659maxBytes: 64 * 1024,
637660});
638661expect(out).toHaveLength(1);
639-expect(out[0]).toMatchObject({ role: "assistant", content: "tail" });
662+expectMessageFields(out[0], { role: "assistant", content: "tail" });
640663expect(readFileSpy).not.toHaveBeenCalled();
641664} finally {
642665readFileSpy.mockRestore();
@@ -659,16 +682,9 @@ describe("readSessionMessages", () => {
659682});
660683661684expect(result.totalMessages).toBe(4);
662-expect(result.messages).toEqual([
663-expect.objectContaining({
664-content: "recent",
665-__openclaw: expect.objectContaining({ seq: 3 }),
666-}),
667-expect.objectContaining({
668-content: "latest",
669-__openclaw: expect.objectContaining({ seq: 4 }),
670-}),
671-]);
685+expect(result.messages).toHaveLength(2);
686+expectMessageFields(result.messages[0], { content: "recent", openclaw: { seq: 3 } });
687+expectMessageFields(result.messages[1], { content: "latest", openclaw: { seq: 4 } });
672688});
673689674690test("preserves real sequence metadata for async bounded recent-message reads", async () => {
@@ -694,16 +710,9 @@ describe("readSessionMessages", () => {
694710);
695711696712expect(result.totalMessages).toBe(4);
697-expect(result.messages).toEqual([
698-expect.objectContaining({
699-content: "recent",
700-__openclaw: expect.objectContaining({ seq: 3 }),
701-}),
702-expect.objectContaining({
703-content: "latest",
704-__openclaw: expect.objectContaining({ seq: 4 }),
705-}),
706-]);
713+expect(result.messages).toHaveLength(2);
714+expectMessageFields(result.messages[0], { content: "recent", openclaw: { seq: 3 } });
715+expectMessageFields(result.messages[1], { content: "latest", openclaw: { seq: 4 } });
707716expect(readFileSpy).not.toHaveBeenCalled();
708717} finally {
709718readFileSpy.mockRestore();
@@ -729,7 +738,8 @@ describe("readSessionMessages", () => {
729738maxBytes: 2048,
730739});
731740732-expect(out).toEqual([expect.objectContaining({ role: "assistant", content: "tail" })]);
741+expect(out).toHaveLength(1);
742+expectMessageFields(out[0], { role: "assistant", content: "tail" });
733743expect(JSON.stringify(out)).not.toContain("huge");
734744expect(readFileSpy).not.toHaveBeenCalled();
735745} finally {
@@ -772,7 +782,8 @@ describe("readSessionMessages", () => {
772782maxBytes: 2048,
773783});
774784775-expect(out).toEqual([expect.objectContaining({ role: "assistant", content: "tail" })]);
785+expect(out).toHaveLength(1);
786+expectMessageFields(out[0], { role: "assistant", content: "tail" });
776787expect(JSON.stringify(out)).not.toContain("huge");
777788expect(readFileSpy).not.toHaveBeenCalled();
778789expect(sessionManagerOpenSpy).not.toHaveBeenCalled();
@@ -865,9 +876,7 @@ describe("readSessionMessages", () => {
865876"active branch",
866877"latest active",
867878]);
868-expect(messages[2]).toMatchObject({
869-__openclaw: expect.objectContaining({ id: "user-2", seq: 3 }),
870-});
879+expectMessageFields(messages[2], { openclaw: { id: "user-2", seq: 3 } });
871880expect(sessionManagerOpenSpy).not.toHaveBeenCalled();
872881expect(readFileSpy).not.toHaveBeenCalled();
873882} finally {
@@ -935,7 +944,7 @@ describe("readSessionMessages", () => {
935944maxBytes: 2048,
936945});
937946expect(messages).toHaveLength(1);
938-expect(messages[0]).toMatchObject({ role: "user", content: "latest" });
947+expectMessageFields(messages[0], { role: "user", content: "latest" });
939948expect(JSON.stringify(messages)).not.toContain("older");
940949expect(openSpy).toHaveBeenCalledTimes(1);
941950} finally {
@@ -960,7 +969,7 @@ describe("readSessionMessages", () => {
9609692048,
961970);
962971963-expect(usage).toMatchObject({
972+expectUsageFields(usage, {
964973inputTokens: 42,
965974outputTokens: 7,
966975});
@@ -989,8 +998,8 @@ describe("readSessionMessages", () => {
9899982048,
990999);
9911000992-expect(aggregate).toMatchObject({ inputTokens: 120, outputTokens: 14 });
993-expect(latest).toMatchObject({ inputTokens: 70, outputTokens: 9 });
1001+expectUsageFields(aggregate, { inputTokens: 120, outputTokens: 14 });
1002+expectUsageFields(latest, { inputTokens: 70, outputTokens: 9 });
9941003});
99510049961005test("tails transcript lines for manual compaction without loading the whole file", () => {
@@ -1078,18 +1087,13 @@ describe("readSessionMessages", () => {
10781087try {
10791088const out = readSessionMessages(sessionId, storePath, sessionFile);
10801089expect(out).toHaveLength(2);
1081-expect(out).toEqual([
1082-expect.objectContaining({
1083-role: "user",
1084-content: "clean prompt",
1085-__openclaw: expect.objectContaining({ seq: 1 }),
1086-}),
1087-expect.objectContaining({
1088-role: "assistant",
1089-content: [{ type: "text", text: "clean answer" }],
1090-__openclaw: expect.objectContaining({ seq: 2 }),
1091-}),
1092-]);
1090+expect(out).toHaveLength(2);
1091+expectMessageFields(out[0], { role: "user", content: "clean prompt", openclaw: { seq: 1 } });
1092+expectMessageFields(out[1], {
1093+role: "assistant",
1094+content: [{ type: "text", text: "clean answer" }],
1095+openclaw: { seq: 2 },
1096+});
10931097expect(JSON.stringify(out)).not.toContain("original wrapped prompt");
10941098expect(sessionManagerOpenSpy).not.toHaveBeenCalled();
10951099} finally {
@@ -1150,7 +1154,7 @@ describe("readSessionMessages", () => {
1150115411511155const out = readSessionMessages(sessionId, wrongStorePath, sessionFile);
11521156expect(out).toHaveLength(1);
1153-expect(out[0]).toMatchObject(message);
1157+expectMessageFields(out[0], message);
11541158expect((out[0] as { __openclaw?: { seq?: number } }).__openclaw?.seq).toBe(1);
11551159},
11561160);
@@ -1590,7 +1594,7 @@ describe("readLatestSessionUsageFromTranscript", () => {
15901594]);
1591159515921596const snapshot = readLatestSessionUsageFromTranscript(sessionId, storePath);
1593-expect(snapshot).toMatchObject({
1597+expectUsageFields(snapshot, {
15941598modelProvider: "anthropic",
15951599model: "claude-sonnet-4-6",
15961600inputTokens: 4200,
@@ -1635,7 +1639,7 @@ describe("readLatestSessionUsageFromTranscript", () => {
1635163916361640try {
16371641const snapshot = await readLatestSessionUsageFromTranscriptAsync(sessionId, storePath);
1638-expect(snapshot).toMatchObject({
1642+expectUsageFields(snapshot, {
16391643modelProvider: "anthropic",
16401644model: "claude-sonnet-4-6",
16411645inputTokens: 4200,
@@ -1686,7 +1690,7 @@ describe("readLatestSessionUsageFromTranscript", () => {
16861690]);
1687169116881692const snapshot = readLatestSessionUsageFromTranscript(sessionId, storePath);
1689-expect(snapshot).toMatchObject({
1693+expectUsageFields(snapshot, {
16901694modelProvider: "openai",
16911695model: "gpt-5.4",
16921696inputTokens: 1500,
@@ -1725,15 +1729,16 @@ describe("readLatestSessionUsageFromTranscript", () => {
17251729const readFileSpy = vi.spyOn(fs, "readFileSync");
1726173017271731try {
1728-expect(
1732+expectUsageFields(
17291733readRecentSessionUsageFromTranscript(sessionId, storePath, undefined, undefined, 64 * 1024),
1730-).toMatchObject({
1731-modelProvider: "openai",
1732-model: "gpt-5.4",
1733-inputTokens: 900,
1734-outputTokens: 100,
1735-totalTokens: 900,
1736-});
1734+{
1735+modelProvider: "openai",
1736+model: "gpt-5.4",
1737+inputTokens: 900,
1738+outputTokens: 100,
1739+totalTokens: 900,
1740+},
1741+);
17371742expect(readFileSpy).not.toHaveBeenCalled();
17381743} finally {
17391744readFileSpy.mockRestore();
@@ -1806,7 +1811,7 @@ describe("resolveSessionTranscriptCandidates safety", () => {
18061811const normalizedCandidates = candidates.map((value) => path.resolve(value));
18071812const expectedFallback = path.resolve(path.dirname(storePath), "sess-safe.jsonl");
180818131809-expect(candidates).not.toEqual(expect.arrayContaining([expect.stringContaining("etc/passwd")]));
1814+expect(candidates.every((candidate) => !candidate.includes("etc/passwd"))).toBe(true);
18101815expect(normalizedCandidates).toContain(expectedFallback);
18111816});
18121817@@ -2056,7 +2061,7 @@ describe("oversized transcript line guards", () => {
20562061512 * 1024,
20572062);
205820632059-expect(usage).toMatchObject({ modelProvider: "test-provider" });
2064+expectUsageFields(usage, { modelProvider: "test-provider" });
20602065});
2061206620622067test("readSessionTitleFieldsFromTranscriptAsync delegates to bounded sync reader", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。