mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-17 12:51:38 +00:00
171 lines
5.2 KiB
TypeScript
171 lines
5.2 KiB
TypeScript
// Qa Lab tests cover jsonl replay plugin behavior.
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
createMockJsonlReplayCellRunner,
|
|
extractJsonlReplayUserTurns,
|
|
renderJsonlReplayMarkdownReport,
|
|
runJsonlReplay,
|
|
type JsonlReplayCellRunner,
|
|
} from "./jsonl-replay.js";
|
|
import type { RuntimeId, RuntimeParityCell, RuntimeParityToolCall } from "./runtime-parity.js";
|
|
|
|
const tempRoots: string[] = [];
|
|
|
|
function makeCell(
|
|
runtime: RuntimeId,
|
|
overrides: Partial<RuntimeParityCell> = {},
|
|
): RuntimeParityCell {
|
|
return {
|
|
runtime,
|
|
transcriptBytes: `{"message":{"role":"assistant","content":"${runtime} reply"}}\n`,
|
|
toolCalls: [],
|
|
finalText: "same reply",
|
|
usage: {
|
|
inputTokens: 10,
|
|
outputTokens: 5,
|
|
totalTokens: 15,
|
|
},
|
|
wallClockMs: 12,
|
|
bootStateLines: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeToolCall(overrides: Partial<RuntimeParityToolCall> = {}): RuntimeParityToolCall {
|
|
return {
|
|
tool: "read",
|
|
argsHash: "args-a",
|
|
resultHash: "result-a",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function makeTempDir() {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "jsonl-replay-"));
|
|
tempRoots.push(tempRoot);
|
|
return tempRoot;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
tempRoots.splice(0).map((tempRoot) => fs.rm(tempRoot, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("jsonl replay", () => {
|
|
it("extracts user-turn boundaries while ignoring system, tool-only, empty, and malformed rows", () => {
|
|
const turns = extractJsonlReplayUserTurns(
|
|
[
|
|
`{"message":{"role":"system","content":"System setup"}}`,
|
|
`{"message":{"role":"tool","content":"tool-only prelude"}}`,
|
|
`{"message":{"role":"user","content":" "}}`,
|
|
`{not-json`,
|
|
`{"message":{"role":"assistant","content":"Ready."}}`,
|
|
`{"message":{"role":"user","content":[{"type":"text","text":"Plan the release"},{"type":"tool_result","content":"ignored"}]}}`,
|
|
`{"role":"user","content":[{"type":"input_text","text":"Check the follow-up"}]}`,
|
|
].join("\n"),
|
|
);
|
|
|
|
expect(turns).toEqual([
|
|
expect.objectContaining({
|
|
turn: 1,
|
|
lineNumber: 6,
|
|
userText: "Plan the release",
|
|
}),
|
|
expect.objectContaining({
|
|
turn: 2,
|
|
lineNumber: 7,
|
|
userText: "Check the follow-up",
|
|
}),
|
|
]);
|
|
expect(turns[0]?.transcriptPrefix).toContain(`"role":"system"`);
|
|
expect(turns[0]?.transcriptPrefix).not.toContain("{not-json");
|
|
});
|
|
|
|
it("reports the earliest divergent turn using runtime parity drift classes", async () => {
|
|
const transcriptDir = await makeTempDir();
|
|
await fs.writeFile(
|
|
path.join(transcriptDir, "three-turns.jsonl"),
|
|
[
|
|
`{"message":{"role":"user","content":"Turn one"}}`,
|
|
`{"message":{"role":"assistant","content":"Ready"}}`,
|
|
`{"message":{"role":"user","content":"Turn two"}}`,
|
|
`{"message":{"role":"assistant","content":"Using a tool"}}`,
|
|
`{"message":{"role":"user","content":"Turn three"}}`,
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
|
|
const runCell: JsonlReplayCellRunner = async ({ runtime, turn }) => {
|
|
if (turn.turn === 2) {
|
|
return {
|
|
scenarioStatus: "pass",
|
|
cell: makeCell(runtime, {
|
|
toolCalls: [makeToolCall(runtime === "openclaw" ? {} : { argsHash: "args-codex" })],
|
|
}),
|
|
};
|
|
}
|
|
if (turn.turn === 3) {
|
|
return {
|
|
scenarioStatus: "pass",
|
|
cell: makeCell(runtime, {
|
|
finalText: runtime === "openclaw" ? "openclaw wording" : "codex wording",
|
|
}),
|
|
};
|
|
}
|
|
return {
|
|
scenarioStatus: "pass",
|
|
cell: makeCell(runtime),
|
|
};
|
|
};
|
|
|
|
const result = await runJsonlReplay(
|
|
{
|
|
directory: transcriptDir,
|
|
runtimePair: ["openclaw", "codex"],
|
|
providerMode: "mock-openai",
|
|
},
|
|
{ runCell },
|
|
);
|
|
|
|
expect(result.transcripts).toHaveLength(1);
|
|
expect(result.transcripts[0]).toEqual(
|
|
expect.objectContaining({
|
|
userTurnCount: 3,
|
|
drift: ["none", "tool-call-shape", "text-only"],
|
|
firstDriftAtTurn: 2,
|
|
}),
|
|
);
|
|
expect(result.transcripts[0]?.cells.openclaw).toHaveLength(3);
|
|
expect(result.transcripts[0]?.cells.codex).toHaveLength(3);
|
|
});
|
|
|
|
it("runs the curated replay fixture set in mock-openai mode", async () => {
|
|
const fixtureDir = path.resolve("qa/scenarios/jsonl-replay");
|
|
|
|
const result = await runJsonlReplay(
|
|
{
|
|
directory: fixtureDir,
|
|
runtimePair: ["openclaw", "codex"],
|
|
providerMode: "mock-openai",
|
|
},
|
|
{ runCell: createMockJsonlReplayCellRunner() },
|
|
);
|
|
|
|
expect(result.transcripts).toHaveLength(7);
|
|
expect(result.transcripts.map((entry) => entry.userTurnCount)).toEqual([2, 2, 3, 2, 2, 2, 2]);
|
|
expect(result.transcripts.every((entry) => entry.firstDriftAtTurn === undefined)).toBe(true);
|
|
expect(
|
|
renderJsonlReplayMarkdownReport({
|
|
generatedAt: "2026-05-10T00:00:00.000Z",
|
|
providerMode: "mock-openai",
|
|
runtimePair: ["openclaw", "codex"],
|
|
transcripts: result.transcripts,
|
|
}),
|
|
).toContain("| plan-mode-boundaries.jsonl | 3 | | none, none, none |");
|
|
});
|
|
});
|