Files
openclaw/extensions/qa-lab/src/runtime-parity.test.ts
Peter Steinberger 5a795f4dda fix(qa): require a fresh reply and real tool use after switching models (#119662)
* fix(qa): require a fresh reply after switching models

* fix(qa): prove successful tool use after model switching

* fix(qa): wait for the new persisted model-switch tool result

* fix(qa): authenticate canonical model-switch attempt evidence

* fix(ai): preserve effective response model evidence

Punchcard-Session: golden-valley-workshop-br

* fix(agent): publish run-owned terminal receipts

Punchcard-Session: golden-valley-workshop-br

* fix(qa): require run-owned model-switch evidence

Punchcard-Session: golden-valley-workshop-br

* fix(agent): record explicit tool completion outcomes

Punchcard-Session: golden-valley-workshop-br

* fix(agent): exclude unavailable approvals from receipts

Punchcard-Session: golden-valley-workshop-br

* fix(agent): derive receipt visibility from terminal reply

Punchcard-Session: golden-valley-workshop-br

* fix(qa): bind model-switch continuity to terminal reply

Punchcard-Session: golden-valley-workshop-br

* fix(qa): project Crabline Telegram visible text

Punchcard-Session: golden-valley-workshop-br

* fix(qa): record run-owned delivery evidence

Punchcard-Session: golden-valley-workshop-br

* fix(qa): bind primary model-switch delivery

Punchcard-Session: golden-valley-workshop-br

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-08 03:05:32 +08:00

1062 lines
32 KiB
TypeScript

// Qa Lab tests cover runtime parity classification behavior.
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import path from "node:path";
import { resolveStorePath, upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime";
import {
appendSqliteTrajectoryRuntimeEvents,
formatSqliteSessionFileMarker,
} from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterEach, describe, expect, it, vi } from "vitest";
import { stableHash } from "./parity-shared.js";
import {
captureRuntimeParityCell,
isRuntimeParityResultPass,
resolveRuntimeParityUsagePolicy,
runRuntimeParityScenario,
type RuntimeId,
type RuntimeParityCell,
type RuntimeParityToolCall,
} from "./runtime-parity.js";
import { createTempDirHarness } from "./temp-dir.test-helper.js";
const tempDirs = createTempDirHarness();
afterEach(async () => {
vi.unstubAllGlobals();
await tempDirs.cleanup();
});
async function seedRuntimeParityTranscript(params: {
heartbeatIsolatedBaseSessionKey?: string;
messages: Array<Record<string, unknown>>;
sessionId: string;
sessionKey: string;
tempRoot?: string;
trajectoryEvents?: Array<{
data?: Record<string, unknown>;
type: string;
}>;
updatedAt?: number;
}) {
const tempRoot = params.tempRoot ?? (await tempDirs.makeTempDir("openclaw-qa-runtime-parity-"));
const env = { ...process.env, OPENCLAW_STATE_DIR: path.join(tempRoot, "state") };
const storePath = resolveStorePath(undefined, { agentId: "qa", env });
await upsertSessionEntry({
agentId: "qa",
env,
sessionKey: params.sessionKey,
storePath,
entry: {
sessionId: params.sessionId,
sessionFile: formatSqliteSessionFileMarker({
agentId: "qa",
sessionId: params.sessionId,
storePath,
}),
updatedAt: params.updatedAt ?? 100,
...(params.heartbeatIsolatedBaseSessionKey
? { heartbeatIsolatedBaseSessionKey: params.heartbeatIsolatedBaseSessionKey }
: {}),
},
});
for (const [index, message] of params.messages.entries()) {
await appendSessionTranscriptMessageByIdentity({
agentId: "qa",
env,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
storePath,
now: index + 1,
message: message as never,
});
}
if (params.trajectoryEvents?.length) {
appendSqliteTrajectoryRuntimeEvents(
{ agentId: "qa", env, sessionId: params.sessionId, storePath },
params.trajectoryEvents.map((event, index) => ({
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
traceId: params.sessionId,
source: "runtime",
type: event.type,
ts: new Date(index + 1).toISOString(),
seq: index + 1,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
runId: "run-1",
data: event.data,
})),
);
}
return tempRoot;
}
async function captureRuntimeParityWithMockRequests(params: {
messages?: Array<Record<string, unknown>>;
requests: Array<Record<string, unknown>>;
scenarioResult?: Parameters<typeof captureRuntimeParityCell>[0]["scenarioResult"];
trajectoryEvents?: Array<{
data?: Record<string, unknown>;
type: string;
}>;
}) {
const parentPrompt = "Delegate one bounded QA task to a subagent.";
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "mock-runtime-parity",
sessionKey: "agent:qa:mock-runtime-parity",
messages: params.messages ?? [{ role: "user", content: parentPrompt }],
trajectoryEvents: params.trajectoryEvents,
});
const requests = params.requests.map((request) => ({
prompt: parentPrompt,
allInputText: parentPrompt,
...request,
}));
const server = createServer((request, response) => {
if (request.url !== "/debug/requests") {
response.statusCode = 404;
response.end();
return;
}
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(requests));
});
await new Promise<void>((resolve) => {
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address() as AddressInfo;
try {
return await captureRuntimeParityCell({
runtime: "openclaw",
gateway: { tempRoot },
mockBaseUrl: `http://127.0.0.1:${address.port}`,
scenarioResult: params.scenarioResult ?? { status: "pass" },
wallClockMs: 10,
});
} finally {
await new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
}
function makeRuntimeParityCell(
runtime: RuntimeId,
toolCalls: RuntimeParityToolCall[],
): RuntimeParityCell {
return {
runtime,
transcriptBytes: '{"message":{"role":"assistant","content":"done"}}\n',
toolCalls,
finalText: "done",
usage: {
inputTokens: 1,
outputTokens: 1,
totalTokens: 2,
},
wallClockMs: 10,
bootStateLines: [],
};
}
describe("runtime parity", () => {
it("cancels a failed mock-request response before falling back to transcript calls", async () => {
const parentPrompt = "Delegate one bounded QA task to a subagent.";
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "mock-runtime-parity-failure",
sessionKey: "agent:qa:mock-runtime-parity-failure",
messages: [{ role: "user", content: parentPrompt }],
});
const cancel = vi.fn(() => {
throw new Error("cancel failed");
});
vi.stubGlobal(
"fetch",
vi.fn(
async () =>
new Response(new ReadableStream<Uint8Array>({ cancel }), {
status: 503,
}),
),
);
const cell = await captureRuntimeParityCell({
runtime: "openclaw",
gateway: { tempRoot },
mockBaseUrl: "http://127.0.0.1:43123",
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cancel).toHaveBeenCalledOnce();
expect(cell.toolCalls).toEqual([]);
});
it("captures tool results from the canonical SQLite session transcript", async () => {
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "capability-flip",
sessionKey: "agent:qa:capability-flip",
messages: [
{
role: "user",
content: [{ type: "text", text: "Capability flip image check" }],
},
{
role: "assistant",
content: [
{
type: "toolCall",
id: "call-image-1",
name: "image_generate",
arguments: { prompt: "QA lighthouse" },
},
],
},
{
role: "toolResult",
toolCallId: "call-image-1",
toolName: "image_generate",
content: [{ type: "text", text: "Image generation started" }],
},
],
});
const cell = await captureRuntimeParityCell({
runtime: "openclaw",
gateway: { tempRoot },
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cell.transcriptBytes).toContain('"role":"toolResult"');
expect(cell.toolCalls).toHaveLength(1);
expect(cell.toolCalls[0]).toMatchObject({ tool: "image_generate" });
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
});
it("captures native tool execution from the canonical SQLite trajectory", async () => {
const cell = await captureRuntimeParityWithMockRequests({
messages: [],
requests: [],
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "search-1",
name: "web_search",
arguments: { query: "OpenClaw runtime parity fixed query" },
},
},
{
type: "tool.result",
data: {
toolCallId: "search-1",
name: "web_search",
status: "completed",
isError: false,
result: {
status: "completed",
query: "OpenClaw runtime parity fixed query",
},
},
},
],
});
expect(cell.toolCalls).toEqual([expect.objectContaining({ tool: "web_search" })]);
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
expect(cell.providerPlanToolCalls).toEqual([]);
});
it("merges trajectory-only calls without duplicating transcript calls", async () => {
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "mixed-runtime-tools",
sessionKey: "agent:qa:mixed-runtime-tools",
messages: [
{ role: "user", content: "Read the file, run the command, then search." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "read-1",
name: "read",
arguments: { path: "README.md" },
},
{
type: "toolCall",
id: "exec-1",
name: "exec",
arguments: { command: "pwd" },
},
],
},
{
role: "toolResult",
toolCallId: "read-1",
toolName: "read",
content: [{ type: "text", text: "README contents" }],
},
],
trajectoryEvents: [
{
type: "tool.result",
data: {
toolCallId: "exec-1",
name: "exec",
success: true,
contentItems: [{ type: "text", text: "/workspace" }],
},
},
{
type: "tool.call",
data: {
toolCallId: "search-1",
name: "web_search",
arguments: { query: "OpenClaw runtime parity fixed query" },
},
},
{
type: "tool.result",
data: {
toolCallId: "search-1",
name: "web_search",
status: "completed",
result: { status: "completed" },
},
},
],
});
const cell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cell.toolCalls.map((toolCall) => toolCall.tool)).toEqual(["read", "exec", "web_search"]);
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
expect(cell.toolCalls[1]?.errorClass).toBeUndefined();
expect(cell.toolCalls[1]?.argsHash).toBe(stableHash({ command: "pwd" }));
});
it("keeps distinct same-tool calls with identical arguments", async () => {
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "distinct-web-searches",
sessionKey: "agent:qa:distinct-web-searches",
messages: [
{ role: "user", content: "Search for both QA markers." },
{
role: "assistant",
content: [
{
type: "toolCall",
name: "web_search",
arguments: { query: "same marker" },
},
],
},
{
role: "toolResult",
toolName: "web_search",
content: [{ type: "text", text: "result A" }],
},
],
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "search-b",
name: "web_search",
arguments: { query: "same marker" },
},
},
{
type: "tool.result",
data: {
toolCallId: "search-b",
name: "web_search",
status: "completed",
result: { status: "completed", query: "same marker" },
},
},
],
});
const cell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cell.toolCalls.map((toolCall) => toolCall.tool)).toEqual(["web_search", "web_search"]);
expect(cell.toolCalls[0]?.argsHash).toBe(cell.toolCalls[1]?.argsHash);
});
it("skips newer trajectory-only heartbeat sessions", async () => {
const now = Date.now();
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "web-search-session",
sessionKey: "agent:qa:web-search-session",
messages: [],
updatedAt: now - 1_000,
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "search-1",
name: "web_search",
arguments: { query: "release marker" },
},
},
{
type: "tool.result",
data: {
toolCallId: "search-1",
name: "web_search",
status: "completed",
result: { status: "completed" },
},
},
],
});
await seedRuntimeParityTranscript({
tempRoot,
sessionId: "heartbeat-session",
sessionKey: "agent:qa:main:heartbeat",
heartbeatIsolatedBaseSessionKey: "agent:qa:main",
messages: [],
updatedAt: now,
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "heartbeat-1",
name: "web_search",
arguments: { query: "heartbeat background search" },
},
},
{
type: "tool.result",
data: {
toolCallId: "heartbeat-1",
name: "web_search",
success: true,
result: { status: "completed" },
},
},
],
});
const cell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cell.toolCalls.map((toolCall) => toolCall.tool)).toEqual(["web_search"]);
expect(cell.toolCalls[0]?.argsHash).toBe(stableHash({ query: "release marker" }));
});
it("captures fixture-owned evidence across multiple root sessions", async () => {
const now = Date.now();
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "session-status-happy",
sessionKey: "agent:qa:runtime-tool:session_status:happy",
messages: [{ role: "user", content: "tool search qa check target=session_status" }],
updatedAt: now - 1_000,
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "session-status-1",
name: "session_status",
arguments: {},
},
},
{
type: "tool.result",
data: {
toolCallId: "session-status-1",
name: "session_status",
status: "completed",
result: { status: "completed" },
},
},
],
});
await seedRuntimeParityTranscript({
tempRoot,
sessionId: "session-status-failure",
sessionKey: "agent:qa:runtime-tool:session_status:failure",
messages: [
{
role: "user",
content: "tool search qa failure target=session_status",
},
],
updatedAt: now,
});
await seedRuntimeParityTranscript({
tempRoot,
sessionId: "unrelated-newer-root",
sessionKey: "agent:qa:unrelated-newer-root",
messages: [{ role: "user", content: "Unrelated setup." }],
updatedAt: now + 1_000,
});
const cell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: [
"RUNTIME_PARITY_SESSION_KEY=agent:qa:runtime-tool:session_status:happy",
"RUNTIME_PARITY_SESSION_KEY=agent:qa:runtime-tool:session_status:failure",
].join("\n"),
},
],
},
wallClockMs: 10,
});
expect(cell.transcriptBytes).toContain("target=session_status");
expect(cell.transcriptBytes).toContain("failure target=session_status");
expect(cell.transcriptBytes).not.toContain("Unrelated setup.");
expect(cell.toolCalls).toEqual([expect.objectContaining({ tool: "session_status" })]);
const missingCell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: {
status: "fail",
details: "RUNTIME_PARITY_SESSION_KEY=agent:qa:runtime-tool:missing:happy",
},
wallClockMs: 10,
});
expect(missingCell.transcriptBytes).toBe("");
expect(missingCell.toolCalls).toEqual([]);
});
it("keeps an explicitly identified orphan result separate", async () => {
const tempRoot = await seedRuntimeParityTranscript({
sessionId: "orphan-trajectory-result",
sessionKey: "agent:qa:orphan-trajectory-result",
messages: [],
trajectoryEvents: [
{
type: "tool.call",
data: {
toolCallId: "read-pending",
name: "read",
arguments: { path: "README.md" },
},
},
{
type: "tool.result",
data: {
toolCallId: "read-orphan",
name: "read",
success: true,
contentItems: [{ type: "text", text: "orphan result" }],
},
},
],
});
const cell = await captureRuntimeParityCell({
runtime: "codex",
gateway: { tempRoot },
scenarioResult: { status: "pass" },
wallClockMs: 10,
});
expect(cell.toolCalls.map((toolCall) => toolCall.errorClass)).toEqual([
"tool-result-missing",
undefined,
]);
});
it("keeps a retry pass diagnostic from failing the captured cell", async () => {
const cell = await captureRuntimeParityCell({
runtime: "openclaw",
gateway: {
tempRoot: `/tmp/openclaw-qa-runtime-parity-missing-${process.pid}`,
},
scenarioResult: {
status: "pass",
details: "ok | passed on retry; first attempt: timed out after 20000ms",
},
wallClockMs: 10,
});
expect(cell.runtimeErrorClass).toBeUndefined();
});
it("still classifies terminal scenario failure diagnostics", async () => {
const cell = await captureRuntimeParityCell({
runtime: "openclaw",
gateway: {
tempRoot: `/tmp/openclaw-qa-runtime-parity-missing-${process.pid}`,
},
scenarioResult: {
status: "fail",
details: "timed out after 20000ms",
},
wallClockMs: 10,
});
expect(cell.runtimeErrorClass).toBe("timeout");
});
it("keeps planned mock calls diagnostic instead of promoting them to runtime calls", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "read_file", plannedToolArgs: { path: "README.md" } }],
});
expect(cell.toolCalls).toEqual([]);
expect(cell.providerPlanToolCalls).toHaveLength(1);
expect(cell.providerPlanToolCalls?.[0]).toMatchObject({
tool: "read_file",
errorClass: "tool-result-missing",
});
});
it("records resolved mock calls as provider-plan evidence", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [
{ plannedToolName: "read_file", plannedToolArgs: { path: "README.md" } },
{ toolOutput: JSON.stringify({ ok: true }) },
],
});
expect(cell.toolCalls).toEqual([]);
expect(cell.providerPlanToolCalls).toHaveLength(1);
expect(cell.providerPlanToolCalls?.[0]?.errorClass).toBeUndefined();
const result = await runRuntimeParityScenario({
scenarioId: "resolved-tool",
runCell: async (runtime) => ({
status: "pass",
cell: { ...cell, runtime },
}),
});
expect(result.drift).toBe("none");
expect(result.runtimeParityUsage).toEqual({
expectation: "assistant-message-required",
});
});
it("preserves explicit usage-not-applicable metadata on parity results", async () => {
const result = await runRuntimeParityScenario({
scenarioId: "local-fixture",
runtimeParityUsage: {
expectation: "not-applicable",
reason: " Local fixture only; no assistant turn runs. ",
},
runCell: async (runtime) => ({
status: "pass",
cell: makeRuntimeParityCell(runtime, []),
}),
});
expect(result.runtimeParityUsage).toEqual({
expectation: "not-applicable",
reason: "Local fixture only; no assistant turn runs.",
});
});
it("defaults malformed usage metadata to assistant-message-required", () => {
expect(resolveRuntimeParityUsagePolicy({ expectation: "not-applicable" })).toEqual({
expectation: "assistant-message-required",
});
expect(
resolveRuntimeParityUsagePolicy({ expectation: "not-applicable", reason: " " }),
).toEqual({ expectation: "assistant-message-required" });
});
it("does not classify planned-only provider evidence as a runtime failure", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "read_file", plannedToolArgs: { path: "README.md" } }],
});
const result = await runRuntimeParityScenario({
scenarioId: "planned-only-tool",
runCell: async (runtime) => ({
status: "pass",
cell: { ...cell, runtime },
}),
});
expect(result.drift).toBe("none");
expect(isRuntimeParityResultPass(result)).toBe(true);
});
it("treats matching controlled tool errors as equivalent results", async () => {
const result = await runRuntimeParityScenario({
scenarioId: "matching-tool-errors",
runCell: async (runtime) => ({
status: "pass",
cell: {
...makeRuntimeParityCell(runtime, [
{
tool: "web_search",
argsHash: "same-args",
resultHash: runtime === "openclaw" ? "validation-error" : "provider-error",
errorClass: "tool-result-error",
},
]),
...(runtime === "codex" ? { runtimeErrorClass: "tool-error" } : {}),
},
}),
});
expect(result.drift).toBe("none");
expect(isRuntimeParityResultPass(result)).toBe(true);
});
it("does not mask runtime cell scenario failures behind drift", async () => {
const result = await runRuntimeParityScenario({
scenarioId: "failed-cell-with-drift",
runCell: async (runtime) => ({
status: runtime === "codex" ? "fail" : "pass",
cell: makeRuntimeParityCell(runtime, [
{
tool: "web_search",
argsHash: "same-args",
resultHash: runtime === "codex" ? "failed-result" : "ok-result",
},
]),
}),
});
expect(result).toMatchObject({
drift: "failure-mode",
driftDetails: "runtime-pair cell status differs (pass vs fail)",
});
expect(isRuntimeParityResultPass(result)).toBe(false);
});
it("prefers transcript tool results when mock debug rows repeat an incomplete call", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } },
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } },
],
messages: [
{ role: "user", content: "Delegate one bounded QA task to a subagent." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "same" },
},
],
},
{
role: "toolResult",
toolCallId: "image-call",
toolName: "image_generate",
content: [{ type: "text", text: "Image generation started" }],
},
],
});
expect(cell.toolCalls).toEqual([expect.objectContaining({ tool: "image_generate" })]);
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
});
it("accepts a fresh scenario MEDIA result for terminal image tools", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } }],
messages: [
{ role: "user", content: "Generate the QA image." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "same" },
},
],
},
],
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: "QA-CAPABILITY-1234\nimage_generate=true\nMEDIA:/tmp/qa-image.png",
},
],
},
});
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
});
it("keeps multiple image provider plans from invalidating one proven runtime call", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "first" } },
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "second" } },
],
messages: [
{ role: "user", content: "Generate the QA image." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "runtime" },
},
],
},
],
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: "QA-CAPABILITY-1234\nimage_generate=true\nMEDIA:/tmp/qa-image.png",
},
],
},
});
expect(cell.toolCalls[0]?.errorClass).toBeUndefined();
expect(cell.providerPlanToolCalls).toHaveLength(2);
});
it("requires call-linked passed step evidence for terminal image results", async () => {
const proven = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } }],
messages: [
{ role: "user", content: "Generate the QA image." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "same" },
},
],
},
],
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: "QA-CAPABILITY-1234\nimage_generate=true\nMEDIA:/tmp/qa-image.png",
},
],
},
});
const unrelated = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } }],
messages: [
{ role: "user", content: "Generate the QA image." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "same" },
},
],
},
],
scenarioResult: {
status: "pass",
steps: [{ status: "pass", details: "MEDIA:/tmp/unrelated-screenshot.png" }],
},
});
const failed = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "same" } }],
messages: [
{ role: "user", content: "Generate the QA image." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "image-call",
name: "image_generate",
arguments: { prompt: "same" },
},
],
},
],
scenarioResult: {
status: "pass",
steps: [
{
status: "fail",
details: "image_generate=true\nMEDIA:/tmp/failed-image.png",
},
],
},
});
expect(proven.toolCalls[0]?.errorClass).toBeUndefined();
expect(unrelated.toolCalls[0]?.errorClass).toBe("tool-result-missing");
expect(failed.toolCalls[0]?.errorClass).toBe("tool-result-missing");
});
it("preserves incomplete image provider plans as diagnostic evidence", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "first" } },
{ toolOutput: JSON.stringify({ ok: true }) },
{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "second" } },
],
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: "image_generate=true\nMEDIA:/tmp/qa-image.png",
},
],
},
});
expect(cell.toolCalls).toEqual([]);
expect(cell.providerPlanToolCalls?.map((toolCall) => toolCall.errorClass)).toEqual([
undefined,
"tool-result-missing",
]);
});
it("preserves missing image results when capture sources disagree on call count", async () => {
const cell = await captureRuntimeParityWithMockRequests({
requests: [{ plannedToolName: "image_generate", plannedToolArgs: { prompt: "first" } }],
messages: [
{ role: "user", content: "Delegate one bounded QA task to a subagent." },
{
role: "assistant",
content: [
{
type: "toolCall",
id: "first-image",
name: "image_generate",
arguments: { prompt: "first" },
},
{
type: "toolCall",
id: "second-image",
name: "image_generate",
arguments: { prompt: "second" },
},
],
},
{
role: "toolResult",
toolCallId: "first-image",
toolName: "image_generate",
content: [{ type: "text", text: "Image generation started" }],
},
],
scenarioResult: {
status: "pass",
steps: [
{
status: "pass",
details: "image_generate=true\nMEDIA:/tmp/qa-image.png",
},
],
},
});
expect(cell.toolCalls.map((toolCall) => toolCall.errorClass)).toEqual([
undefined,
"tool-result-missing",
]);
});
it("scopes process-global mock requests to the parent session prompt", async () => {
const cell = await captureRuntimeParityWithMockRequests({
messages: [
{ role: "user", content: "Delegate one bounded QA task to a subagent." },
{
role: "user",
content: "Continue the bounded QA task with the retained child result.",
},
],
requests: [
{
prompt: "Fanout worker alpha: inspect the QA workspace and finish with exactly ALPHA-OK.",
allInputText:
"Delegate one bounded QA task to a subagent. Fanout worker alpha: inspect the QA workspace and finish with exactly ALPHA-OK.",
plannedToolName: "read",
},
{
prompt: "Delegate one bounded QA task to a subagent.",
allInputText: "Delegate one bounded QA task to a subagent.",
plannedToolName: "sessions_spawn",
},
{
prompt: "Continue the bounded QA task with the retained child result.",
allInputText:
"Delegate one bounded QA task to a subagent. Continue the bounded QA task with the retained child result.",
plannedToolName: "sessions_spawn",
},
{
prompt: undefined,
allInputText: "Inspect the QA workspace and return one concise protocol note.",
plannedToolName: "read",
},
{
prompt: "Delegate one bounded QA task to a subagent.",
allInputText: "Delegate one bounded QA task to a subagent. Tool result: child accepted.",
toolOutput: "child accepted",
},
],
});
expect(cell.toolCalls).toEqual([]);
expect(cell.providerPlanToolCalls).toHaveLength(2);
expect(cell.providerPlanToolCalls?.map((toolCall) => toolCall.tool)).toEqual([
"sessions_spawn",
"sessions_spawn",
]);
expect(cell.providerPlanToolCalls?.map((toolCall) => toolCall.errorClass)).toEqual([
undefined,
"tool-result-missing",
]);
});
it("copies model-switch evidence into the runtime parity cell", async () => {
const modelSwitchEvidence = {
primary: { runId: "run-1", responseModel: "primary-model" },
alternate: { runId: "run-2", responseModel: "alternate-model" },
};
const cell = await captureRuntimeParityWithMockRequests({
requests: [],
scenarioResult: { status: "pass", modelSwitchEvidence },
});
expect(cell.modelSwitchEvidence).toEqual(modelSwitchEvidence);
});
});