
























@@ -317,17 +317,35 @@ describe("exportTrajectoryBundle", () => {
317317writeSimpleSessionFile(sessionFile);
318318fs.writeFileSync(
319319runtimeFile,
320-`${JSON.stringify({})}\n${JSON.stringify({
321- traceSchema: "openclaw-trajectory",
322- schemaVersion: 1,
323- traceId: "session-1",
324- source: "runtime",
325- type: "session.started",
326- ts: "2026-04-22T08:00:00.000Z",
327- seq: 1,
328- sourceSeq: 1,
329- sessionId: "session-1",
330- })}\n`,
320+[
321+"",
322+JSON.stringify({}),
323+"",
324+JSON.stringify({
325+traceSchema: "openclaw-trajectory",
326+schemaVersion: 1,
327+traceId: "session-1",
328+source: "runtime",
329+type: "bad-data",
330+ts: "2026-04-22T08:00:00.000Z",
331+seq: 1,
332+sourceSeq: 1,
333+sessionId: "session-1",
334+data: [],
335+}),
336+'{"traceSchema":',
337+JSON.stringify({
338+traceSchema: "openclaw-trajectory",
339+schemaVersion: 1,
340+traceId: "session-1",
341+source: "runtime",
342+type: "session.started",
343+ts: "2026-04-22T08:00:00.000Z",
344+seq: 1,
345+sourceSeq: 1,
346+sessionId: "session-1",
347+}),
348+].join("\n") + "\n",
331349"utf8",
332350);
333351@@ -340,6 +358,95 @@ describe("exportTrajectoryBundle", () => {
340358341359expect(bundle.manifest.runtimeEventCount).toBe(1);
342360expect(eventTypes(bundle.events)).toContain("session.started");
361+expect(bundle.manifest.warnings).toEqual([
362+{
363+source: "runtime",
364+code: "invalid-runtime-event",
365+count: 2,
366+rows: [2, 4],
367+message: "Skipped a runtime trajectory JSONL row that does not match the session schema.",
368+},
369+{
370+source: "runtime",
371+code: "invalid-runtime-json",
372+count: 1,
373+rows: [5],
374+message: "Skipped a runtime trajectory JSONL row that is not valid JSON.",
375+},
376+]);
377+});
378+379+it("skips and reports malformed session jsonl rows without poisoning transcript export", async () => {
380+const tmpDir = makeTempDir();
381+const sessionFile = path.join(tmpDir, "session.jsonl");
382+const outputDir = path.join(tmpDir, "bundle");
383+const header = {
384+type: "session",
385+version: 3,
386+id: "session-1",
387+timestamp: "2026-04-01T05:46:39.000Z",
388+cwd: tmpDir,
389+};
390+const userEntry = {
391+type: "message",
392+id: "entry-user",
393+parentId: null,
394+timestamp: "2026-04-01T05:46:40.000Z",
395+message: userMessage("hello"),
396+};
397+const assistantEntry = {
398+type: "message",
399+id: "entry-assistant",
400+parentId: "entry-user",
401+timestamp: "2026-04-01T05:46:41.000Z",
402+message: assistantMessage([{ type: "text", text: "done" }]),
403+};
404+fs.writeFileSync(
405+sessionFile,
406+[
407+JSON.stringify(header),
408+"null",
409+'{"type":',
410+JSON.stringify({
411+type: "message",
412+id: "entry-corrupt",
413+parentId: null,
414+timestamp: "2026-04-01T05:46:39.500Z",
415+}),
416+JSON.stringify(userEntry),
417+JSON.stringify(assistantEntry),
418+].join("\n") + "\n",
419+"utf8",
420+);
421+422+const bundle = await exportTrajectoryBundle({
423+ outputDir,
424+ sessionFile,
425+sessionId: "session-1",
426+workspaceDir: tmpDir,
427+});
428+429+expect(bundle.manifest.transcriptEventCount).toBe(2);
430+expect(eventTypes(bundle.events)).toEqual(["user.message", "assistant.message"]);
431+expect(bundle.manifest.warnings).toEqual([
432+{
433+source: "session",
434+code: "invalid-session-row",
435+count: 2,
436+rows: [2, 4],
437+message: "Skipped a session JSONL row that is not a session entry object.",
438+},
439+{
440+source: "session",
441+code: "invalid-session-json",
442+count: 1,
443+rows: [3],
444+message: "Skipped a session JSONL row that is not valid JSON.",
445+},
446+]);
447+expect(
448+JSON.parse(fs.readFileSync(path.join(outputDir, "manifest.json"), "utf8")).warnings,
449+).toEqual(bundle.manifest.warnings);
343450});
344451345452it("uses the recorded runtime pointer before current environment overrides", async () => {
@@ -550,6 +657,7 @@ describe("exportTrajectoryBundle", () => {
550657551658expect(bundle.manifest.runtimeEventCount).toBe(0);
552659expect(eventTypes(bundle.events)).not.toContain("other-runtime");
660+expect(bundle.manifest.warnings).toBeUndefined();
553661});
554662555663it("redacts non-workspace paths in strings that also contain workspace paths", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。