fix(qa): repair release validation scenarios (#119150)

* fix(qa): repair parity and matrix media setup

Punchcard-Session: silver-valley-valley-dt

* test(qa): accept token-budgeted compaction suffix

Punchcard-Session: silver-valley-valley-dt

* test(qa): scope request diagnostics

Punchcard-Session: silver-valley-valley-dt

* test(qa): prove thread-memory causality

Punchcard-Session: silver-valley-valley-dt

* test(qa): configure Matrix image generation and fail fast

Punchcard-Session: silver-valley-valley-dt

* fix(qa): require memory get before thread recall

Punchcard-Session: silver-valley-valley-dt

* fix(qa): unify mock provider dispatch

Punchcard-Session: silver-valley-valley-dt

* fix(qa): repair Anthropic IDs and compaction causality

Punchcard-Session: silver-valley-valley-dt

* fix(qa): authenticate compaction wire causality

Punchcard-Session: silver-valley-valley-dt

* fix(qa): preserve Responses tool item identity

Punchcard-Session: silver-valley-valley-dt

* fix(qa): scope restart heartbeat proof

Punchcard-Session: silver-valley-valley-dt

* test(qa): assert causal traces before delivery

Punchcard-Session: silver-valley-valley-dt

* fix(qa): validate code mode completion evidence

Punchcard-Session: silver-valley-valley-dt

* test(qa): split causal catalog checks

Punchcard-Session: silver-valley-valley-dt

* style(qa): format matrix scenario tests

Punchcard-Session: silver-valley-valley-dt
This commit is contained in:
Vincent Koc
2026-08-05 09:56:06 +08:00
committed by GitHub
parent 8d2996937f
commit 6a8e11e63c
26 changed files with 1903 additions and 528 deletions
@@ -5,6 +5,7 @@ import {
readQaScenarioExecutionConfig,
} from "../../scenario-catalog.js";
import { requireFlowScenario } from "../../scenario-catalog.test-utils.js";
import { collectQaSuitePluginIds } from "../../suite-planning.js";
const MATRIX_MENTION_GATE_PRIMARY_SCENARIOS = [
"matrix-allowbots-default-block",
@@ -221,14 +222,17 @@ describe("Matrix QA Lab scenario flows", () => {
});
it("loads the voice preflight provider and media overrides", () => {
expect(readQaScenarioById("matrix-voice-preflight-mention").plugins).toEqual(["openai"]);
expect(readQaScenarioById("matrix-voice-preflight-mention").execution).toMatchObject({
const scenario = readQaScenarioById("matrix-voice-preflight-mention");
expect(scenario.execution).toMatchObject({
kind: "flow",
providerMode: "mock-openai",
retryCount: 0,
timeoutMs: 90_000,
});
expect(readQaScenarioById("matrix-voice-preflight-mention").gatewayConfigPatch).toMatchObject({
expect(scenario.plugins).toEqual(["openai"]);
expect(collectQaSuitePluginIds([scenario])).toEqual(["openai"]);
expect(scenario.gatewayConfigPatch).toMatchObject({
tools: {
media: {
models: [{ capabilities: ["audio"], model: "gpt-4o-transcribe", provider: "openai" }],
@@ -274,4 +278,37 @@ describe("Matrix QA Lab scenario flows", () => {
},
});
});
it("configures image generation before the single generated-image flow call", () => {
const scenario = requireFlowScenario(
readQaScenarioById("matrix-room-generated-image-delivery"),
);
const actions = scenario.execution.flow?.steps[0]?.actions ?? [];
expect(scenario.execution).toMatchObject({
channel: "matrix",
retryCount: 0,
timeoutMs: 180_000,
config: {
requiredChannelDriver: "live",
},
});
expect(actions).toEqual([
{
call: "ensureImageGenerationConfigured",
args: [{ ref: "env" }],
},
{
set: "scenarioModule",
value: {
expr: "await qaImport('./live-transports/matrix/scenarios/scenario-runtime-media.js')",
},
},
{
call: "scenarioModule.runGeneratedImageDeliveryScenario",
args: [{ expr: "scenarioContext" }],
saveAs: "result",
},
]);
});
});
@@ -1,5 +1,16 @@
import { describe, expect, it } from "vitest";
import { testing } from "./scenario-runtime-media.js";
import { afterEach, describe, expect, it, vi } from "vitest";
const advanceMatrixQaActorCursor = vi.hoisted(() => vi.fn());
const primeMatrixQaActorCursor = vi.hoisted(() => vi.fn());
vi.mock("./scenario-runtime-shared.js", async (importOriginal) => ({
...(await importOriginal<typeof import("./scenario-runtime-shared.js")>()),
advanceMatrixQaActorCursor,
primeMatrixQaActorCursor,
}));
import { runGeneratedImageDeliveryScenario, testing } from "./scenario-runtime-media.js";
import type { MatrixQaScenarioContext } from "./scenario-runtime-shared.js";
describe("Matrix voice preflight reply matching", () => {
it("accepts punctuation differences in the transcribed marker", () => {
@@ -10,3 +21,120 @@ describe("Matrix voice preflight reply matching", () => {
).toBe(true);
});
});
const roomId = "!media:matrix-qa.test";
function createGeneratedImageContext(
observedEvents: MatrixQaScenarioContext["observedEvents"] = [],
): MatrixQaScenarioContext {
return {
baseUrl: "http://127.0.0.1:28008",
driverAccessToken: "driver-token",
driverUserId: "@driver:matrix-qa.test",
observedEvents,
observerAccessToken: "observer-token",
observerUserId: "@observer:matrix-qa.test",
roomId,
sutAccessToken: "sut-token",
sutUserId: "@sut:matrix-qa.test",
syncState: {},
timeoutMs: 180_000,
topology: {
defaultRoomId: roomId,
defaultRoomKey: "media",
rooms: [
{
encrypted: false,
key: "media",
kind: "group",
memberRoles: ["driver", "observer", "sut"],
memberUserIds: [
"@driver:matrix-qa.test",
"@observer:matrix-qa.test",
"@sut:matrix-qa.test",
],
name: "Matrix QA Media Room",
requireMention: true,
roomId,
},
],
},
};
}
describe("Matrix generated image delivery", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("sends one trigger and accepts one fresh top-level image", async () => {
const sendTextMessage = vi.fn(async () => "$driver-trigger");
const waitForOptionalRoomEvent = vi.fn(async ({ predicate }) => {
const event = {
attachment: { filename: "generated.png", kind: "image" as const },
eventId: "$generated-image",
kind: "message" as const,
msgtype: "m.image",
originServerTs: Date.now() + 1,
roomId,
sender: "@sut:matrix-qa.test",
type: "m.room.message",
};
expect(predicate(event)).toBe(true);
return { event, matched: true as const, since: "next" };
});
primeMatrixQaActorCursor.mockResolvedValue({
client: { sendTextMessage, waitForOptionalRoomEvent },
startSince: "start",
});
const result = await runGeneratedImageDeliveryScenario(createGeneratedImageContext());
expect(sendTextMessage).toHaveBeenCalledTimes(1);
expect(waitForOptionalRoomEvent).toHaveBeenCalledTimes(1);
expect(result.artifacts?.driverEventId).toBe("$driver-trigger");
expect(result.artifacts?.attachmentEventId).toBe("$generated-image");
});
it("bounds timeout diagnostics to the last eight room events", async () => {
const observedEvents = Array.from({ length: 10 }, (_, index) => ({
body: `event body ${index + 1}`,
eventId: `$event-${index + 1}`,
kind: "message" as const,
roomId,
sender: "@sut:matrix-qa.test",
type: "m.room.message",
}));
observedEvents.push({
body: "other room",
eventId: "$other-room",
kind: "message",
roomId: "!other:matrix-qa.test",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
});
const sendTextMessage = vi.fn(async () => "$driver-trigger");
primeMatrixQaActorCursor.mockResolvedValue({
client: {
sendTextMessage,
waitForOptionalRoomEvent: vi.fn(async () => ({
matched: false as const,
since: "next",
})),
},
startSince: "start",
});
const error = await runGeneratedImageDeliveryScenario(
createGeneratedImageContext(observedEvents),
).catch((cause: unknown) => cause);
const message = error instanceof Error ? error.message : String(error);
expect(sendTextMessage).toHaveBeenCalledTimes(1);
expect(message).toContain("$event-3");
expect(message).toContain("$event-10");
expect(message).not.toContain('$event-1"');
expect(message).not.toContain('$event-2"');
expect(message).not.toContain("$other-room");
});
});
@@ -425,41 +425,43 @@ export async function runGeneratedImageDeliveryScenario(context: MatrixQaScenari
const roomId = resolveMatrixQaScenarioRoomId(context, MATRIX_QA_MEDIA_ROOM_KEY);
const { client, startSince } = await primeMatrixQaDriverMediaClient(context);
const triggerBody = buildMatrixQaImageGenerationPrompt(context.sutUserId);
const driverEventIds: string[] = [];
const triggerSentAt = Date.now();
const driverEventId = await client.sendTextMessage({
body: triggerBody,
mentionUserIds: [context.sutUserId],
roomId,
});
const isGeneratedImageEvent = (event: MatrixQaObservedEvent) =>
event.roomId === roomId &&
event.sender === context.sutUserId &&
event.type === "m.room.message" &&
event.relatesTo === undefined &&
event.msgtype === "m.image" &&
event.attachment?.kind === "image";
let matched: Awaited<ReturnType<typeof client.waitForOptionalRoomEvent>> | undefined;
for (let attempt = 1; attempt <= 2; attempt += 1) {
const triggerSentAt = Date.now();
const driverEventId = await client.sendTextMessage({
body: triggerBody,
mentionUserIds: [context.sutUserId],
roomId,
});
driverEventIds.push(driverEventId);
matched = await client.waitForOptionalRoomEvent({
observedEvents: context.observedEvents,
// The start cursor can still receive delayed images from an earlier run.
predicate: (event) =>
isGeneratedImageEvent(event) &&
typeof event.originServerTs === "number" &&
event.originServerTs >= triggerSentAt,
roomId,
since: matched?.since ?? startSince,
timeoutMs: context.timeoutMs,
});
if (matched.matched) {
break;
}
}
if (!matched?.matched) {
event.attachment?.kind === "image" &&
typeof event.originServerTs === "number" &&
event.originServerTs >= triggerSentAt;
const matched = await client.waitForOptionalRoomEvent({
observedEvents: context.observedEvents,
// The start cursor can still receive delayed images from an earlier run.
predicate: isGeneratedImageEvent,
roomId,
since: startSince,
timeoutMs: context.timeoutMs,
});
if (!matched.matched) {
const recentRoomEvents = context.observedEvents
.filter((event) => event.roomId === roomId)
.slice(-8)
.map((event) => ({
body: truncateMatrixQaPreview(event.body),
eventId: event.eventId,
kind: event.kind,
msgtype: event.msgtype,
sender: event.sender,
type: event.type,
}));
throw new Error(
`timed out after ${context.timeoutMs}ms waiting for Matrix generated image after ${driverEventIds.length} attempt(s)`,
`timed out after ${context.timeoutMs}ms waiting for Matrix generated image; recent room events: ${JSON.stringify(recentRoomEvents)}`,
);
}
const matchedEvent = matched.event;
@@ -480,14 +482,13 @@ export async function runGeneratedImageDeliveryScenario(context: MatrixQaScenari
attachmentFilename: attachment.filename,
attachmentKind: attachment.kind,
attachmentMsgtype: matchedEvent.msgtype,
driverEventId: driverEventIds[0],
driverEventIds,
driverEventId,
roomId,
triggerBody,
},
details: [
`room id: ${roomId}`,
`driver events: ${driverEventIds.join(", ")}`,
`driver event: ${driverEventId}`,
...buildMatrixQaAttachmentDetailLines({
attachmentEvent: matchedEvent,
label: "generated image",
@@ -83,6 +83,35 @@ describe("matrix qa config", () => {
expect(next.messages?.groupChat?.visibleReplies).toBe("automatic");
});
it("preserves the scenario provider plugin without enabling unrelated plugins", () => {
const next = buildMatrixQaConfig(
{
plugins: {
allow: ["acpx", "memory-core", "qa-lab", "openai"],
entries: {
openai: { enabled: true },
unrelated: { enabled: true },
},
},
} as OpenClawConfig,
{
driverUserId: "@driver:matrix-qa.test",
homeserver: "http://127.0.0.1:28008/",
observerUserId: "@observer:matrix-qa.test",
sutAccessToken: "sut-token",
sutAccountId: "sut",
sutUserId: "@sut:matrix-qa.test",
topology,
},
);
expect(next.plugins?.allow).toEqual(["acpx", "memory-core", "qa-lab", "openai", "matrix"]);
expect(next.plugins?.allow).not.toContain("unrelated");
expect(next.plugins?.allow).not.toContain("anthropic");
expect(next.plugins?.entries?.matrix).toEqual({ enabled: true });
expect(next.plugins?.entries?.openai).toEqual({ enabled: true });
});
it("honors an explicit DM disable with a provisioned DM room", () => {
const next = buildMatrixQaConfig({} as OpenClawConfig, {
driverUserId: "@driver:matrix-qa.test",
@@ -1,109 +1,70 @@
// QA Lab Anthropic Messages wire adapter.
import {
convertAnthropicMessagesToResponsesInput,
type ExtractedAssistantOutput,
extractFinalAssistantOutputFromEvents,
buildAnthropicFailureResponse,
buildAnthropicMessageResponse,
buildAnthropicMessageStreamEvents,
buildAnthropicThinkingErrorResponse,
buildAnthropicThinkingErrorStreamEvents,
buildAnthropicMessageStreamEvents,
convertAnthropicMessagesToResponsesInput,
extractFinalAssistantOutputFromEvents,
normalizeAnthropicSystemToString,
} from "./mock-anthropic-wire.js";
// QA Lab Anthropic Messages request dispatcher.
import {
type ResponsesInputItem,
type StreamEvent,
type AnthropicMessagesRequest,
QA_ANTHROPIC_THINKING_ERROR_RECOVERY_PROMPT_RE,
type MockScenarioState,
type AnthropicStreamEvent,
import type {
AnthropicMessagesRequest,
AnthropicStreamEvent,
QaMockProviderDispatchResult,
ResponsesInputItem,
} from "./mock-openai-contracts.js";
import { buildAssistantEvents } from "./mock-openai-events.js";
import {
extractAllRequestTexts,
extractLastUserText,
extractToolOutput,
extractToolOutputCallId,
} from "./mock-openai-input.js";
import { buildToolCallEventsWithArgs } from "./mock-openai-tooling.js";
export async function buildMessagesPayload(
body: AnthropicMessagesRequest,
scenarioState: MockScenarioState,
dispatchResponses: (
body: Record<string, unknown>,
scenarioState: MockScenarioState,
) => Promise<StreamEvent[]>,
): Promise<{
events: StreamEvent[];
export function normalizeAnthropicMessagesRequest(body: AnthropicMessagesRequest): {
body: Record<string, unknown>;
input: ResponsesInputItem[];
extracted: ExtractedAssistantOutput;
model: string;
} {
const model =
typeof body.model === "string" && body.model.trim() !== "" ? body.model : "claude-opus-4-8";
const input = convertAnthropicMessagesToResponsesInput({
messages: Array.isArray(body.messages) ? body.messages : [],
});
const instructions = normalizeAnthropicSystemToString(body.system);
return {
body: {
input,
model,
stream: false,
...(instructions ? { instructions } : {}),
...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
},
input,
model,
};
}
export function buildMessagesPayload(dispatched: QaMockProviderDispatchResult): {
responseBody: Record<string, unknown>;
streamEvents: AnthropicStreamEvent[];
model: string;
}> {
const messages = Array.isArray(body.messages) ? body.messages : [];
const input = convertAnthropicMessagesToResponsesInput({
system: body.system,
messages,
});
// Treat empty-string model the same as absent. A bare typeof check lets
// `""` leak through to `responseBody.model` and `lastRequest.model`,
// which then confuses parity consumers that assume the mock always
// echoes the real provider label. Normalize once and reuse everywhere.
const normalizedModel =
typeof body.model === "string" && body.model.trim() !== "" ? body.model : "claude-opus-4-8";
// Dispatch through the same scenario logic the /v1/responses route uses.
// Preserve declared tools so route-specific adapters mirror what the
// real provider request made available to the model.
const dispatchBody: Record<string, unknown> = {
input,
model: normalizedModel,
stream: false,
...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
};
const allInputText = extractAllRequestTexts(input, dispatchBody);
if (QA_ANTHROPIC_THINKING_ERROR_RECOVERY_PROMPT_RE.test(allInputText)) {
const toolOutput = extractToolOutput(input);
const toolOutputCallId = extractToolOutputCallId(input);
const scenarioKey = `${normalizedModel}\n${extractLastUserText(input)}`;
const shouldEmitThinkingError =
toolOutput.length > 0 &&
toolOutputCallId.length > 0 &&
!scenarioState.anthropicThinkingErrorScenarioKeys.has(scenarioKey);
// Safe retries generate fresh read call IDs. The original user prompt stays
// stable, so fail once per model and nonce-bearing logical scenario instead.
if (shouldEmitThinkingError) {
scenarioState.anthropicThinkingErrorScenarioKeys.add(scenarioKey);
}
const events =
toolOutput.length === 0
? buildToolCallEventsWithArgs("read", { path: "QA_KICKOFF_TASK.md" })
: shouldEmitThinkingError
? buildAssistantEvents("")
: buildAssistantEvents("ANTHROPIC-THINKING-ERROR-RECOVERED-OK");
const extracted = extractFinalAssistantOutputFromEvents(events);
const responseBody = shouldEmitThinkingError
? buildAnthropicThinkingErrorResponse({ model: normalizedModel })
: buildAnthropicMessageResponse({
model: normalizedModel,
extracted,
});
const streamEvents = shouldEmitThinkingError
? buildAnthropicThinkingErrorStreamEvents({ model: normalizedModel })
: buildAnthropicMessageStreamEvents({
model: normalizedModel,
extracted,
});
return { events, input, extracted, responseBody, streamEvents, model: normalizedModel };
} {
if (dispatched.failure?.presentation === "anthropic-thinking") {
return {
responseBody: buildAnthropicThinkingErrorResponse({ model: dispatched.model }),
streamEvents: buildAnthropicThinkingErrorStreamEvents({ model: dispatched.model }),
};
}
const events = await dispatchResponses(dispatchBody, scenarioState);
const extracted = extractFinalAssistantOutputFromEvents(events);
const responseBody = buildAnthropicMessageResponse({
model: normalizedModel,
extracted,
});
const streamEvents = buildAnthropicMessageStreamEvents({
model: normalizedModel,
extracted,
});
return { events, input, extracted, responseBody, streamEvents, model: normalizedModel };
if (dispatched.failure) {
return {
responseBody: buildAnthropicFailureResponse(dispatched.failure),
streamEvents: [],
};
}
const extracted = extractFinalAssistantOutputFromEvents(dispatched.events);
return {
responseBody: buildAnthropicMessageResponse({
model: dispatched.model,
extracted,
}),
streamEvents: buildAnthropicMessageStreamEvents({
model: dispatched.model,
extracted,
}),
};
}
@@ -1,4 +1,5 @@
// QA Lab Anthropic Messages wire conversion and response events.
import { createHash } from "node:crypto";
import {
type ResponsesInputItem,
type StreamEvent,
@@ -6,13 +7,14 @@ import {
type AnthropicMessage,
type AnthropicMessagesRequest,
type AnthropicStreamEvent,
type QaMockProviderFailure,
countApproxTokens,
} from "./mock-openai-contracts.js";
// Anthropic Messages conversion preserves role and tool ordering while reusing
// the shared Responses scenario dispatcher for provider parity.
function normalizeAnthropicSystemToString(
export function normalizeAnthropicSystemToString(
system: AnthropicMessagesRequest["system"],
): string | undefined {
if (typeof system === "string") {
@@ -145,11 +147,62 @@ export function convertAnthropicMessagesToResponsesInput(params: {
return items;
}
export type ExtractedAssistantOutput = {
type ExtractedAssistantOutput = {
text: string;
toolCalls: Array<{ id: string; name: string; input: Record<string, unknown> }>;
};
const NATIVE_ANTHROPIC_TOOL_USE_ID_RE = /^toolu_[A-Za-z0-9_]+$/;
const ANTHROPIC_TOOL_USE_ID_MAX_LENGTH = 64;
function isNativeAnthropicToolUseId(id: string): boolean {
return id.length <= ANTHROPIC_TOOL_USE_ID_MAX_LENGTH && NATIVE_ANTHROPIC_TOOL_USE_ID_RE.test(id);
}
export function adaptAnthropicToolCallIds(events: StreamEvent[]): StreamEvent[] {
const adaptedIds = new Map<string, string>();
const adaptId = (id: string) => {
if (isNativeAnthropicToolUseId(id)) {
return id;
}
const existing = adaptedIds.get(id);
if (existing) {
return existing;
}
const adapted = `toolu_${createHash("sha256").update(id).digest("hex").slice(0, 48)}`;
adaptedIds.set(id, adapted);
return adapted;
};
const adaptItem = (item: Record<string, unknown>) => {
if (
(item.type === "function_call" || item.type === "custom_tool_call") &&
typeof item.call_id === "string"
) {
return { ...item, call_id: adaptId(item.call_id) };
}
return item;
};
return events.map((event) => {
if (event.type === "response.output_item.added" || event.type === "response.output_item.done") {
return { ...event, item: adaptItem(event.item) };
}
if (event.type === "response.custom_tool_call_input.delta") {
return { ...event, call_id: adaptId(event.call_id) };
}
if (event.type === "response.completed") {
return {
...event,
response: {
...event.response,
output: event.response.output.map(adaptItem),
},
};
}
return event;
});
}
export function extractFinalAssistantOutputFromEvents(
events: StreamEvent[],
): ExtractedAssistantOutput {
@@ -237,6 +290,19 @@ export function buildAnthropicMessageResponse(params: {
};
}
export function buildAnthropicFailureResponse(
failure: QaMockProviderFailure,
): Record<string, unknown> {
return {
type: "error",
error: {
type: failure.type,
...(failure.code ? { code: failure.code } : {}),
message: failure.message,
},
};
}
const QA_ANTHROPIC_THINKING_ERROR_TEXT =
"QA replay-safe read completed, but the provider stream failed after signed thinking.";
const QA_ANTHROPIC_THINKING_ERROR_SIGNATURE = "qa_signed_thinking_block_91953";
@@ -15,6 +15,28 @@ export type MockCompactionSummaryFaultMode =
type MockOpenAiRequestOutcome = "success" | "error";
export type QaMockProviderDispatchRequest = {
route: "responses" | "anthropic-messages";
body: Record<string, unknown>;
raw: string;
};
export type QaMockProviderFailure = {
status: number;
type: string;
code?: string;
message: string;
presentation?: "anthropic-thinking";
};
export type QaMockProviderDispatchResult = {
events: StreamEvent[];
model: string;
failure?: QaMockProviderFailure;
onResponseSent?: () => void;
previewPauseMs?: number;
};
export type StreamEvent =
| { type: "response.created"; response: { id: string } }
| {
@@ -136,6 +158,7 @@ export type MockOpenAiRequestSnapshot = {
errorCode?: string;
rawByteLength: number;
plannedToolCallId?: string;
plannedToolItemId?: string;
plannedToolName?: string;
plannedWireToolName?: string;
plannedToolArgs?: Record<string, unknown>;
@@ -108,20 +108,26 @@ export function extractPlannedToolName(events: StreamEvent[]) {
return undefined;
}
export function extractPlannedToolCallId(events: StreamEvent[]) {
export function extractPlannedToolIdentity(events: StreamEvent[]): {
callId?: string;
itemId?: string;
} {
for (const event of events) {
if (event.type !== "response.output_item.done") {
continue;
}
const item = event.item as { type?: unknown; call_id?: unknown };
const item = event.item as { type?: unknown; id?: unknown; call_id?: unknown };
if (
(item.type === "function_call" || item.type === "custom_tool_call") &&
typeof item.call_id === "string"
) {
return item.call_id;
return {
callId: item.call_id,
itemId: typeof item.id === "string" ? item.id : undefined,
};
}
}
return undefined;
return {};
}
export function extractPlannedToolArgs(events: StreamEvent[]) {
@@ -158,6 +158,9 @@ export function extractToolOutput(input: ResponsesInputItem[]) {
return item ? stringifyFunctionCallOutput(item.output) : "";
}
export const extractToolOutputValue = (input: ResponsesInputItem[]) =>
findCurrentToolOutput(input)?.output;
export function extractToolOutputStructuredError(input: ResponsesInputItem[]) {
const item = findCurrentToolOutput(input);
return item?.is_error === true || item?.isError === true;
@@ -264,7 +267,7 @@ export function extractSlackMpimRetainedBotNonce(
return undefined;
}
export function extractAllInputTexts(input: ResponsesInputItem[]) {
function extractAllInputTexts(input: ResponsesInputItem[]) {
const texts: string[] = [];
for (const item of input) {
if (typeof item.output === "string" && item.output.trim()) {
@@ -1,24 +1,12 @@
import type { Server } from "node:http";
import { setTimeout as sleep } from "node:timers/promises";
import { WebSocket, WebSocketServer, type RawData } from "ws";
import type { ResponsesInputItem, StreamEvent } from "./mock-openai-contracts.js";
export type QaMockResponsesDispatchResult = {
events: StreamEvent[];
failure?: {
status: number;
type: string;
code?: string;
message: string;
};
onResponseSent?: () => void;
previewPauseMs?: number;
};
import type { QaMockProviderDispatchResult, ResponsesInputItem } from "./mock-openai-contracts.js";
type QaMockResponsesWebSocketDispatch = (params: {
body: Record<string, unknown>;
raw: string;
}) => Promise<QaMockResponsesDispatchResult>;
}) => Promise<QaMockProviderDispatchResult>;
type QaMockResponsesWebSocketHistory = {
id: string;
@@ -3,6 +3,8 @@ import { once } from "node:events";
import { afterEach, describe, expect, it } from "vitest";
import { WebSocket } from "ws";
import { readQaMockRequestCursor } from "../shared/debug-request-cursor.js";
import { adaptAnthropicToolCallIds } from "./mock-anthropic-wire.js";
import type { StreamEvent } from "./mock-openai-contracts.js";
import { readTargetFromPrompt } from "./mock-openai-tooling.js";
import { startQaMockOpenAiServer } from "./server.js";
@@ -2315,6 +2317,65 @@ describe("qa mock openai server", () => {
expect(requests.every((request) => request.errorCode === "context_length_exceeded")).toBe(true);
});
it("injects one Anthropic overflow per session before planning the logical write", async () => {
const server = await startMockServer();
const bodyFor = (sessionId: string) => ({
system: `Runtime: embedded | sessionId=${sessionId}`,
tools: [
{
name: "exec",
input_schema: {
type: "object",
properties: {
language: { type: "string" },
code: { type: "string" },
},
required: ["code"],
},
},
{
name: "wait",
input_schema: {
type: "object",
properties: { runId: { type: "string" } },
required: ["runId"],
},
},
],
messages: [
makeAnthropicUserText(
`${QA_COMPACTION_RETRY_PROMPT}\n${QA_COMPACTION_RETRY_OVERFLOW_PADDING}`,
),
],
});
const first = await postAnthropicMessages(server, bodyFor("anthropic-overflow-a"));
expect(first.status).toBe(400);
expect(await first.json()).toEqual({
type: "error",
error: {
type: "invalid_request_error",
code: "context_length_exceeded",
message: "This model's maximum context length was exceeded.",
},
});
const second = await postAnthropicMessages(server, bodyFor("anthropic-overflow-a"));
expect(second.status).toBe(200);
const content = requireArray(
requireRecord(await second.json(), "Anthropic response").content,
"content",
);
expect(content).toContainEqual(expect.objectContaining({ type: "tool_use", name: "exec" }));
expect(await getJson(server, "/debug/last-request")).toMatchObject({
plannedToolName: "write",
plannedWireToolName: "exec",
});
const independent = await postAnthropicMessages(server, bodyFor("anthropic-overflow-b"));
expect(independent.status).toBe(400);
});
it("excludes compaction summary requests from overflow injection", async () => {
const server = await startMockServer();
const initial = await postNonStreamingResponses(server, {
@@ -2496,6 +2557,28 @@ describe("qa mock openai server", () => {
]);
});
it("excludes Anthropic compaction summary requests from overflow injection", async () => {
const server = await startMockServer();
const response = await postAnthropicMessages(server, {
system: QA_COMPACTION_SUMMARY_INSTRUCTIONS,
messages: [
makeAnthropicUserText(
`<conversation>\n${QA_COMPACTION_RETRY_OVERFLOW_PADDING}\n</conversation>`,
),
],
});
expect(response.status).toBe(200);
const body = requireRecord(await response.json(), "Anthropic summary response");
expect(requireArray(body.content, "content")).toContainEqual(
expect.objectContaining({ type: "text", text: expect.stringContaining("## Goal") }),
);
expect(await getJson(server, "/debug/last-request")).toMatchObject({
requestKind: "compaction-summary",
outcome: "success",
});
});
it("handles staged scalar compaction summaries and promotes the durable merge without state leakage", async () => {
const server = await startMockServer();
const genericChunkPayload = await expectOpenAiNonStreamingResponsesJson(server, {
@@ -3549,6 +3632,115 @@ Update and merge these partial structured summaries.`,
expect(imageRequest.size).toBe("1024x1024");
});
it("requires memory_get before answering thread recall in Code Mode", async () => {
const server = await startMockServer();
const prompt =
"@openclaw Thread memory check: what is the hidden thread codename stored only in memory? Use memory tools first and reply only in this thread.";
const codeModeTools = [
{
type: "function",
name: "exec",
parameters: {
type: "object",
properties: {
language: { type: "string" },
code: { type: "string" },
},
required: ["code"],
},
},
{
type: "function",
name: "wait",
parameters: {
type: "object",
properties: { runId: { type: "string" } },
required: ["runId"],
},
},
];
const initialInput: Array<Record<string, unknown>> = [
{
type: "additional_tools",
role: "developer",
tools: codeModeTools,
},
makeUserInput(prompt),
];
const searchPlan = await expectOpenAiNonStreamingResponsesJson(server, {
input: initialInput,
});
const searchCall = outputToolCall(searchPlan, "exec");
const searchCallId = outputToolCallId(searchCall, "call_mock_memory_search");
const continuationInput: Array<Record<string, unknown>> = [
makeUserInput(prompt),
searchCall,
makeToolOutputWithCallId(
searchCallId,
JSON.stringify({
status: "completed",
value: {
results: [
{
path: "MEMORY.md",
startLine: 1,
endLine: 1,
snippet: "Thread-hidden codename: ORBIT-21.",
},
],
},
}),
),
];
const getPlan = await expectOpenAiNonStreamingResponsesJson(server, {
input: continuationInput,
});
const getCall = outputToolCall(getPlan, "memory_get");
const getCallId = outputToolCallId(getCall, "call_mock_memory_get");
expect(getCallId).not.toBe(searchCallId);
expect(outputItems(getPlan).some((item) => item.type === "message")).toBe(false);
expect(JSON.stringify(getPlan)).not.toContain("hidden thread codename is ORBIT-21");
continuationInput.push(
getCall,
makeToolOutputWithCallId(
getCallId,
JSON.stringify({
status: "completed",
value: { text: "Thread-hidden codename: ORBIT-22." },
}),
),
);
const final = await expectOpenAiNonStreamingResponsesJson(server, {
input: continuationInput,
});
expect(outputText(final)).toContain("ORBIT-22");
expect(outputText(final)).not.toContain("ORBIT-21");
expect(outputItems(final).some((item) => item.type === "function_call")).toBe(false);
const requests = requireArray(await getJson(server, "/debug/requests"), "debug requests").map(
(request, index) => requireRecord(request, `debug request ${index}`),
);
expect(requests).toHaveLength(3);
expect(requests[0]).toMatchObject({
plannedToolName: "memory_search",
plannedWireToolName: "exec",
plannedToolCallId: searchCallId,
});
expect(requests[1]).toMatchObject({
toolOutputCallId: searchCallId,
plannedToolName: "memory_get",
plannedToolCallId: getCallId,
});
expect(requests[1]).not.toHaveProperty("plannedWireToolName");
expect(requests[2]).toMatchObject({
toolOutputCallId: getCallId,
});
expect(requests[2]).not.toHaveProperty("plannedToolName");
});
it("supports advanced QA memory and subagent recovery prompts", async () => {
const server = await startMockServer();
@@ -3570,6 +3762,29 @@ Update and merge these partial structured summaries.`,
expect(threadMemorySearchText).toContain('"name":"memory_search"');
expect(threadMemorySearchText).toContain("ORBIT-22");
const threadMemoryGetText = await expectStreamingResponsesText(server, {
instructions:
"@openclaw Thread memory check: what is the hidden thread codename stored only in memory? Use memory tools first and reply only in this thread.",
input: [
makeToolOutput(
JSON.stringify({
results: [
{
path: "MEMORY.md",
startLine: 1,
endLine: 1,
snippet: "Thread-hidden codename: ORBIT-22.",
},
],
}),
),
makeUserInput("Protocol note: acknowledged. Continue with the QA scenario plan."),
],
});
expect(threadMemoryGetText).toContain('"name":"memory_get"');
expect(threadMemoryGetText).toContain('\\"path\\":\\"MEMORY.md\\"');
expect(threadMemoryGetText).not.toContain("hidden thread codename is ORBIT-22");
const threadMemorySummary = await expectNonStreamingResponses(server, {
instructions:
"@openclaw Thread memory check: what is the hidden thread codename stored only in memory? Use memory tools first and reply only in this thread.",
@@ -3584,17 +3799,17 @@ Update and merge these partial structured summaries.`,
});
expect(JSON.stringify(await threadMemorySummary.json())).toContain("ORBIT-22");
const structuredThreadMemorySummary = await expectNonStreamingResponses(server, {
const rawThreadMemorySummary = await expectNonStreamingResponses(server, {
instructions:
"@openclaw Thread memory check: what is the hidden thread codename stored only in memory? Use memory tools first and reply only in this thread.",
input: [
makeToolOutput({
text: "Thread-hidden codename: ORBIT-22.",
}),
makeToolOutput("Thread-hidden codename: ORBIT-23."),
makeUserInput("Protocol note: acknowledged. Continue with the QA scenario plan."),
],
});
expect(JSON.stringify(await structuredThreadMemorySummary.json())).toContain("ORBIT-22");
const rawThreadMemoryText = JSON.stringify(await rawThreadMemorySummary.json());
expect(rawThreadMemoryText).toContain("NONE");
expect(rawThreadMemoryText).not.toContain("ORBIT-23");
const unavailableThreadMemorySummary = await expectNonStreamingResponses(server, {
input: [
@@ -5213,7 +5428,8 @@ Update and merge these partial structured summaries.`,
});
const payload = await response.json();
expect(outputItem(payload)).toMatchObject({ type: "function_call", name: "apply_patch" });
const item = outputItem(payload);
expect(item).toMatchObject({ type: "function_call", name: "apply_patch" });
const args = outputToolArgs(payload);
expect(args).not.toHaveProperty("__qaFailureMode");
expect(args.input).toBeTypeOf("string");
@@ -5223,6 +5439,9 @@ Update and merge these partial structured summaries.`,
expect(args.input).toContain("\n@@\n-runtime-tool-fixture-denied-original\n");
}
expect(args.input).toContain("\n*** End Patch\n");
const debug = requireRecord(await getJson(server, "/debug/last-request"), "function plan");
expect(debug.plannedToolCallId).toBe(item.call_id);
expect(debug.plannedToolItemId).toBe(item.id);
});
it.each([
@@ -5265,6 +5484,7 @@ Update and merge these partial structured summaries.`,
);
expect(debug.plannedToolName).toBe("apply_patch");
expect(debug.plannedToolCallId).toBe(item.call_id);
expect(debug.plannedToolItemId).toBe(item.id);
expect(debug.plannedToolArgs).toEqual({ input: item.input });
});
@@ -6081,6 +6301,16 @@ Update and merge these partial structured summaries.`,
const server = await startMockServer();
const body = (await expectAnthropicMessagesJson(server, {
tools: [
{
name: "read",
input_schema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
],
messages: [
makeAnthropicUserText(
"Read the seeded docs and report worked, failed, blocked, and follow-up items.",
@@ -6098,8 +6328,10 @@ Update and merge these partial structured summaries.`,
expect(body.model).toBe("claude-opus-4-8");
expect(body.stop_reason).toBe("tool_use");
const toolUseBlock = body.content.find((block) => block.type === "tool_use") as
| { name: string; input: Record<string, unknown> }
| { id: string; name: string; input: Record<string, unknown> }
| undefined;
expect(toolUseBlock?.id).toMatch(/^toolu_[A-Za-z0-9_]+$/);
expect(toolUseBlock?.id.length).toBeLessThanOrEqual(64);
expect(toolUseBlock?.name).toBe("read");
expect(toolUseBlock?.input).toEqual({ path: "repo/docs/help/testing.md" });
@@ -6108,9 +6340,85 @@ Update and merge these partial structured summaries.`,
"debug request",
);
expect(debugPayload.model).toBe("claude-opus-4-8");
expect(debugPayload.plannedToolCallId).toBe(toolUseBlock?.id);
expect(debugPayload).not.toHaveProperty("plannedToolItemId");
expect(debugPayload.plannedToolName).toBe("read");
});
it("preserves already-native Anthropic tool IDs while adapting shared generated IDs", () => {
const nativeId = "toolu_native_123";
const generatedId = "call_mock_read_generated_1";
const events: StreamEvent[] = [
{
type: "response.output_item.added",
item: { type: "function_call", name: "read", call_id: nativeId, arguments: "{}" },
},
{
type: "response.output_item.done",
item: { type: "function_call", name: "read", call_id: nativeId, arguments: "{}" },
},
{
type: "response.output_item.added",
item: { type: "function_call", name: "read", call_id: generatedId, arguments: "{}" },
},
{
type: "response.output_item.done",
item: { type: "function_call", name: "read", call_id: generatedId, arguments: "{}" },
},
{
type: "response.completed",
response: {
id: "response_mock",
status: "completed",
output: [
{ type: "function_call", name: "read", call_id: nativeId, arguments: "{}" },
{ type: "function_call", name: "read", call_id: generatedId, arguments: "{}" },
],
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
},
},
];
const adapted = adaptAnthropicToolCallIds(events);
const callIds = adapted.flatMap((event) => {
if (
event.type === "response.output_item.added" ||
event.type === "response.output_item.done"
) {
return typeof event.item.call_id === "string" ? [event.item.call_id] : [];
}
if (event.type === "response.completed") {
return event.response.output.flatMap((item) =>
typeof item.call_id === "string" ? [item.call_id] : [],
);
}
return [];
});
const adaptedGeneratedIds = callIds.filter((id) => id !== nativeId);
const repeatedGeneratedIds = adaptAnthropicToolCallIds(events).flatMap((event) => {
if (
event.type === "response.output_item.added" ||
event.type === "response.output_item.done"
) {
return event.item.call_id === nativeId || typeof event.item.call_id !== "string"
? []
: [event.item.call_id];
}
if (event.type === "response.completed") {
return event.response.output.flatMap((item) =>
item.call_id === nativeId || typeof item.call_id !== "string" ? [] : [item.call_id],
);
}
return [];
});
expect(callIds.filter((id) => id === nativeId)).toHaveLength(3);
expect(new Set(adaptedGeneratedIds).size).toBe(1);
expect(repeatedGeneratedIds).toEqual(adaptedGeneratedIds);
expect(adaptedGeneratedIds[0]).toMatch(/^toolu_[A-Za-z0-9_]+$/);
expect(adaptedGeneratedIds[0]?.length).toBeLessThanOrEqual(64);
});
it("routes Anthropic hidden tools through Code Mode and preserves scenario evidence", async () => {
const server = await startMockServer();
const prompt =
@@ -6137,16 +6445,24 @@ Update and merge these partial structured summaries.`,
},
];
const messages: Array<Record<string, unknown>> = [makeAnthropicUserText(prompt)];
const emittedToolUseIds: string[] = [];
const request = async () => {
const request = async (expectedToolResultId?: string) => {
const response = await expectAnthropicMessages(server, {
tools,
messages,
});
return (await response.json()) as {
const body = (await response.json()) as {
stop_reason: string;
content: Array<Record<string, unknown>>;
};
const debug = requireRecord(await getJson(server, "/debug/last-request"), "debug request");
if (expectedToolResultId) {
expect(debug.toolOutputCallId).toBe(expectedToolResultId);
} else {
expect(debug).not.toHaveProperty("toolOutputCallId");
}
return body;
};
const readToolUse = (body: {
stop_reason: string;
@@ -6157,6 +6473,9 @@ Update and merge these partial structured summaries.`,
if (!toolUse || typeof toolUse.id !== "string" || typeof toolUse.name !== "string") {
throw new Error("Expected Anthropic tool_use block");
}
expect(toolUse.id).toMatch(/^toolu_[A-Za-z0-9_]+$/);
expect(toolUse.id.length).toBeLessThanOrEqual(64);
emittedToolUseIds.push(toolUse.id);
return toolUse;
};
const appendToolResult = (
@@ -6168,8 +6487,14 @@ Update and merge these partial structured summaries.`,
makeAnthropicToolResult(toolUse.id, JSON.stringify(result)),
);
};
const expectPlan = async (name: string, args: Record<string, unknown>, wireName = "exec") => {
const expectPlan = async (
name: string,
args: Record<string, unknown>,
callId: string,
wireName = "exec",
) => {
const debug = requireRecord(await getJson(server, "/debug/last-request"), "debug request");
expect(debug.plannedToolCallId).toBe(callId);
expect(debug.plannedToolName).toBe(name);
expect(debug.plannedWireToolName).toBe(wireName);
expect(debug.plannedToolArgs).toEqual(args);
@@ -6180,15 +6505,16 @@ Update and merge these partial structured summaries.`,
const readAgentCode = String(requireRecord(readAgent.input, "exec input").code);
expect(readAgentCode).toContain("tools.callValue(target.id, targetArgs)");
expect(readAgentCode).toContain("value.content.slice(0, 2048)");
await expectPlan("read", { path: "AGENT.md" });
await expectPlan("read", { path: "AGENT.md" }, String(readAgent.id));
appendToolResult(readAgent, { status: "waiting", runId: "qa-code-mode-read-agent" });
const waitForAgent = readToolUse(await request());
const waitForAgent = readToolUse(await request(String(readAgent.id)));
expect(waitForAgent.name).toBe("wait");
const waitDebug = requireRecord(
await fetch(`${server.baseUrl}/debug/last-request`).then((response) => response.json()),
"wait debug request",
);
expect(waitDebug.plannedToolCallId).toBe(waitForAgent.id);
expect(waitDebug.plannedToolName).toBe("wait");
expect(waitDebug).not.toHaveProperty("plannedWireToolName");
expect(waitDebug.plannedToolArgs).toEqual({ runId: "qa-code-mode-read-agent" });
@@ -6197,17 +6523,17 @@ Update and merge these partial structured summaries.`,
status: "completed",
value: { kind: "text", content: "# Repo contract\nDo not stop after planning." },
});
const readSoul = readToolUse(await request());
const readSoul = readToolUse(await request(String(waitForAgent.id)));
expect(readSoul.name).toBe("exec");
await expectPlan("read", { path: "SOUL.md" });
await expectPlan("read", { path: "SOUL.md" }, String(readSoul.id));
appendToolResult(readSoul, {
status: "completed",
value: { kind: "text", content: "# Execution style\nStay action-first." },
});
const readInput = readToolUse(await request());
const readInput = readToolUse(await request(String(readSoul.id)));
expect(readInput.name).toBe("exec");
await expectPlan("read", { path: "FOLLOWTHROUGH_INPUT.md" });
await expectPlan("read", { path: "FOLLOWTHROUGH_INPUT.md" }, String(readInput.id));
appendToolResult(readInput, {
status: "completed",
@@ -6217,24 +6543,215 @@ Update and merge these partial structured summaries.`,
"Mission: prove you followed the repo contract.\nEvidence path: AGENT.md -> SOUL.md -> FOLLOWTHROUGH_INPUT.md -> repo-contract-summary.txt",
},
});
const writeSummary = readToolUse(await request());
const writeSummary = readToolUse(await request(String(readInput.id)));
expect(writeSummary.name).toBe("exec");
await expectPlan("write", {
path: "repo-contract-summary.txt",
content:
"Mission: prove you followed the repo contract.\nEvidence: AGENT.md -> SOUL.md -> FOLLOWTHROUGH_INPUT.md\nStatus: complete",
});
await expectPlan(
"write",
{
path: "repo-contract-summary.txt",
content:
"Mission: prove you followed the repo contract.\nEvidence: AGENT.md -> SOUL.md -> FOLLOWTHROUGH_INPUT.md\nStatus: complete",
},
String(writeSummary.id),
);
appendToolResult(writeSummary, {
status: "completed",
value: "Successfully wrote 146 bytes to repo-contract-summary.txt.",
});
const final = await request();
const final = await request(String(writeSummary.id));
expect(final.stop_reason).toBe("end_turn");
const text = final.content.find((block) => block.type === "text")?.text;
expect(text).toBe(
"Read: AGENT.md, SOUL.md, FOLLOWTHROUGH_INPUT.md\nWrote: repo-contract-summary.txt\nStatus: complete",
);
expect(new Set(emittedToolUseIds).size).toBe(emittedToolUseIds.length);
});
it("uses native Codex custom exec, output arrays, and cell_id waits", async () => {
const server = await startMockServer();
const tools = [
{ type: "custom", name: "exec", format: { type: "grammar", syntax: "lark", definition: "" } },
{
type: "function",
name: "wait",
parameters: {
type: "object",
properties: { cell_id: { type: "string" } },
required: ["cell_id"],
},
},
];
const prompt = QA_COMPACTION_RETRY_PROMPT;
const execPayload = await expectOpenAiNonStreamingResponsesJson(server, {
tools,
input: [makeUserInput(prompt)],
});
const execCall = outputItem(execPayload);
expect(execCall).toMatchObject({ type: "custom_tool_call", name: "exec" });
const source = String(execCall.input);
expect(source).toContain("tools[target.name](targetArgs)");
expect(source).toContain("text(JSON.stringify(value));");
expect(source).not.toContain("tools.callValue");
expect(source).not.toContain("target.id");
expect(source).not.toMatch(/ALL_TOOLS[^\n]*\.id/);
expect(source).not.toContain("return value");
const execCallId = outputToolCallId(execCall, "native-exec");
const waitPayload = await expectOpenAiNonStreamingResponsesJson(server, {
tools,
input: [
makeUserInput(prompt),
execCall,
{
type: "custom_tool_call_output",
call_id: execCallId,
output: [
{
type: "input_text",
text: "Script running with cell ID cell-write-1\nLive output:\n",
},
],
},
],
});
const waitCall = outputToolCall(waitPayload, "wait");
expect(outputToolArgsFromItem(waitCall)).toEqual({ cell_id: "cell-write-1" });
const finalPayload = await expectOpenAiNonStreamingResponsesJson(server, {
tools,
input: [
makeUserInput(prompt),
execCall,
{
type: "custom_tool_call_output",
call_id: execCallId,
output: [
{
type: "input_text",
text: "Script running with cell ID cell-write-1\nLive output:\n",
},
],
},
waitCall,
{
type: "function_call_output",
call_id: outputToolCallId(waitCall, "native-wait"),
output: [
{ type: "input_text", text: "Script completed\nWall time: 0.1 seconds\nOutput:\n" },
{
type: "input_text",
text: JSON.stringify({ status: "completed", value: { changed: false } }),
},
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
],
},
],
});
expect(outputText(finalPayload)).toBe("Protocol note: replay unsafe after write.");
});
it.each([
{
label: "failed header with canonical JSON",
output: [
{ type: "input_text", text: "Script failed\nWall time: 0.1 seconds\nOutput:\n" },
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
],
},
{
label: "terminated header with canonical JSON",
output: [
{ type: "input_text", text: "Script terminated\nWall time: 0.1 seconds\nOutput:\n" },
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
],
},
{
label: "unknown header with canonical JSON",
output: [
{ type: "input_text", text: "Script paused\nWall time: 0.1 seconds\nOutput:\n" },
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
],
},
{
label: "failed header followed by a running marker",
output: [
{ type: "input_text", text: "Script failed\nWall time: 0.1 seconds\nOutput:\n" },
{
type: "input_text",
text: "Script running with cell ID cell-write-late\nLive output:\n",
},
],
},
{
label: "missing header with canonical JSON",
output: [
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
],
},
{
label: "reordered completed header and canonical JSON",
output: [
{
type: "input_text",
text: JSON.stringify(QA_COMPACTION_RETRY_CODE_MODE_WRITE_RESULT),
},
{ type: "input_text", text: "Script completed\nWall time: 0.1 seconds\nOutput:\n" },
],
},
{
label: "completed header without JSON",
output: [{ type: "input_text", text: "Script completed\nWall time: 0.1 seconds\nOutput:\n" }],
},
])("rejects native Code Mode compaction evidence with $label", async ({ output }) => {
const server = await startMockServer();
const tools = [
{ type: "custom", name: "exec", format: { type: "grammar", syntax: "lark", definition: "" } },
{
type: "function",
name: "wait",
parameters: {
type: "object",
properties: { cell_id: { type: "string" } },
required: ["cell_id"],
},
},
];
const execPayload = await expectOpenAiNonStreamingResponsesJson(server, {
tools,
input: [makeUserInput(QA_COMPACTION_RETRY_PROMPT)],
});
const execCall = outputItem(execPayload);
const payload = await expectOpenAiNonStreamingResponsesJson(server, {
tools,
input: [
makeUserInput(QA_COMPACTION_RETRY_PROMPT),
execCall,
{
type: "custom_tool_call_output",
call_id: outputToolCallId(execCall, "native-exec"),
output,
},
],
});
expect(outputItems(payload).some((item) => item.type === "function_call")).toBe(false);
expect(outputText(payload)).not.toBe("Protocol note: replay unsafe after write.");
});
it("routes Anthropic image generation through Code Mode when only exec and wait are visible", async () => {
@@ -6336,70 +6853,83 @@ Update and merge these partial structured summaries.`,
if (!readToolUse || typeof readToolUse.id !== "string") {
throw new Error("Expected Anthropic read tool_use block");
}
messages.push(
{ role: "assistant", content: [readToolUse] },
makeAnthropicToolResult(
readToolUse.id,
JSON.stringify({ status: "waiting", runId: "ordinary-read" }),
),
);
const body = (await expectAnthropicMessagesJson(server, {
tools,
messages,
})) as {
stop_reason: string;
content: Array<Record<string, unknown>>;
};
expect(body.stop_reason).toBe("end_turn");
expect(body.content.some((block) => block.type === "tool_use")).toBe(false);
for (const result of [
{ status: "waiting", runId: "ordinary-read" },
{
status: "completed",
value: { status: "waiting", runId: "ordinary-read-completed-value" },
},
]) {
const body = (await expectAnthropicMessagesJson(server, {
tools,
messages: [
...messages,
{ role: "assistant", content: [readToolUse] },
makeAnthropicToolResult(readToolUse.id, JSON.stringify(result)),
],
})) as {
stop_reason: string;
content: Array<Record<string, unknown>>;
};
expect(body.stop_reason).toBe("end_turn");
expect(body.content.some((block) => block.type === "tool_use")).toBe(false);
}
});
it("does not interpret unmarked direct exec results as Code Mode control envelopes", async () => {
const server = await startMockServer();
const body = (await expectAnthropicMessagesJson(server, {
tools: [
{
name: "exec",
input_schema: {
type: "object",
properties: { code: { type: "string" } },
required: ["code"],
const tools = [
{
name: "exec",
input_schema: {
type: "object",
properties: { code: { type: "string" } },
required: ["code"],
},
},
{
name: "wait",
input_schema: {
type: "object",
properties: { runId: { type: "string" } },
required: ["runId"],
},
},
];
const messages = [
makeAnthropicUserText("Direct exec envelope isolation check."),
{
role: "assistant",
content: [
{
type: "tool_use",
id: "toolu_direct_exec",
name: "exec",
input: { language: "javascript", code: "return 1;" },
},
},
{
name: "wait",
input_schema: {
type: "object",
properties: { runId: { type: "string" } },
required: ["runId"],
},
},
],
messages: [
makeAnthropicUserText("Direct exec envelope isolation check."),
{
role: "assistant",
content: [
{
type: "tool_use",
id: "toolu_direct_exec",
name: "exec",
input: { language: "javascript", code: "return 1;" },
},
],
},
makeAnthropicToolResult(
"toolu_direct_exec",
JSON.stringify({ status: "waiting", runId: "direct-exec" }),
),
],
})) as {
stop_reason: string;
content: Array<Record<string, unknown>>;
};
expect(body.stop_reason).toBe("end_turn");
expect(body.content.some((block) => block.type === "tool_use")).toBe(false);
],
},
];
for (const result of [
{ status: "waiting", runId: "direct-exec" },
{
status: "completed",
value: { status: "waiting", runId: "direct-exec-completed-value" },
},
]) {
const body = (await expectAnthropicMessagesJson(server, {
tools,
messages: [
...messages,
makeAnthropicToolResult("toolu_direct_exec", JSON.stringify(result)),
],
})) as {
stop_reason: string;
content: Array<Record<string, unknown>>;
};
expect(body.stop_reason).toBe("end_turn");
expect(body.content.some((block) => block.type === "tool_use")).toBe(false);
}
});
it("finishes Anthropic Code Mode fanout after the second wrapped spawn result", async () => {
@@ -6914,6 +7444,16 @@ Update and merge these partial structured summaries.`,
).map((request) => requireRecord(request, "Anthropic debug request"));
expect(debugRequests).toHaveLength(8);
expect(debugRequests.every((request) => request.providerVariant === "anthropic")).toBe(true);
expect(debugRequests.map((request) => request.plannedToolCallId)).toEqual([
readCallIds[0],
undefined,
readCallIds[1],
undefined,
readCallIds[2],
undefined,
readCallIds[3],
undefined,
]);
expect(debugRequests.map((request) => request.toolOutputCallId)).toEqual([
undefined,
readCallIds[0],
@@ -6941,6 +7481,16 @@ Update and merge these partial structured summaries.`,
const response = await expectAnthropicMessages(server, {
stream: true,
tools: [
{
name: "read",
input_schema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
],
messages: [
makeAnthropicUserText(
"Read the seeded docs and report worked, failed, blocked, and follow-up items.",
@@ -6956,6 +7506,25 @@ Update and merge these partial structured summaries.`,
expect(body).toContain("repo/docs/help/testing.md");
expect(body).toContain("event: message_delta");
expect(body).toContain("event: message_stop");
const events = body
.split("\n")
.filter((line) => line.startsWith("data: "))
.map((line) =>
requireRecord(JSON.parse(line.slice("data: ".length)) as unknown, "Anthropic SSE event"),
);
const toolUseStart = events.find(
(event) =>
event.type === "content_block_start" &&
requireRecord(event.content_block, "Anthropic SSE content block").type === "tool_use",
);
const toolUse = requireRecord(
toolUseStart?.content_block,
"Anthropic SSE tool_use content block",
);
expect(toolUse.id).toMatch(/^toolu_[A-Za-z0-9_]+$/);
expect(String(toolUse.id).length).toBeLessThanOrEqual(64);
const debug = requireRecord(await getJson(server, "/debug/last-request"), "debug request");
expect(debug.plannedToolCallId).toBe(toolUse.id);
});
it("streams Anthropic /v1/messages tool_result follow-ups as text deltas", async () => {
@@ -8,8 +8,11 @@ import {
listMockCodexModelInfos,
listMockOpenAiServerModelIds,
} from "../shared/mock-model-config.js";
import { buildMessagesPayload } from "./mock-anthropic-messages.js";
import { convertAnthropicMessagesToResponsesInput } from "./mock-anthropic-wire.js";
import {
buildMessagesPayload,
normalizeAnthropicMessagesRequest,
} from "./mock-anthropic-messages.js";
import { adaptAnthropicToolCallIds } from "./mock-anthropic-wire.js";
import {
buildAssistantText,
isCanonicalCompactionRetryWriteResult,
@@ -24,9 +27,12 @@ import {
type MockOpenAiRequestKind,
type MockCompactionSummaryFaultMode,
type AnthropicMessagesRequest,
type QaMockProviderDispatchRequest,
type QaMockProviderDispatchResult,
TINY_PNG_BASE64,
QA_REASONING_ONLY_RECOVERY_PROMPT_RE,
QA_REASONING_ONLY_SIDE_EFFECT_PROMPT_RE,
QA_ANTHROPIC_THINKING_ERROR_RECOVERY_PROMPT_RE,
QA_THINKING_VISIBILITY_OFF_PROMPT_RE,
QA_THINKING_VISIBILITY_MAX_PROMPT_RE,
QA_EMPTY_RESPONSE_RECOVERY_PROMPT_RE,
@@ -125,7 +131,7 @@ import {
buildReleaseAuditJson,
buildReleaseHandoffMarkdown,
extractPlannedToolName,
extractPlannedToolCallId,
extractPlannedToolIdentity,
extractPlannedToolArgs,
splitMockStreamingText,
buildQaLongFinalText,
@@ -139,6 +145,7 @@ import {
extractLastMatchingUserTurn,
hasToolOutput,
extractToolOutput,
extractToolOutputValue,
extractToolOutputStructuredError,
extractToolOutputCallId,
extractLatestToolOutput,
@@ -146,7 +153,6 @@ import {
extractUserTextAfterLatestToolOutput,
extractSlackMpimRetainedBotNonce,
extractAllUserTexts,
extractAllInputTexts,
extractInstructionsText,
extractAllRequestTexts,
buildWhatsAppPendingHistoryReply,
@@ -157,10 +163,7 @@ import {
extractCurrentImageRequest,
parseToolOutputJson,
} from "./mock-openai-input.js";
import {
attachQaMockResponsesWebSocketServer,
type QaMockResponsesDispatchResult,
} from "./mock-openai-responses-websocket.js";
import { attachQaMockResponsesWebSocketServer } from "./mock-openai-responses-websocket.js";
import {
readTargetFromPrompt,
execCommandFromToolProgressPrompt,
@@ -391,31 +394,40 @@ function findNamedToolDefinition(
return null;
}
function hasCodeModeExecSurface(body: Record<string, unknown>) {
type CodeModeExecSurface = "native" | "guest";
function resolveCodeModeExecSurface(body: Record<string, unknown>): CodeModeExecSurface | null {
const tools = [
...(Array.isArray(body.tools) ? body.tools : []),
...(Array.isArray(body.dynamicTools) ? body.dynamicTools : []),
];
const execDefinition = findNamedToolDefinition(tools, "exec");
if (!execDefinition || !hasToolDefinition(body, "wait")) {
return false;
return null;
}
if (execDefinition.type === "custom") {
return "native";
}
const schema =
(execDefinition.input_schema as Record<string, unknown> | undefined) ??
(execDefinition.parameters as Record<string, unknown> | undefined);
if (!schema) {
return false;
return null;
}
const properties = schema.properties;
const required = schema.required;
return (
properties !== null &&
return properties !== null &&
typeof properties === "object" &&
!Array.isArray(properties) &&
Object.hasOwn(properties, "code") &&
Array.isArray(required) &&
required.includes("code")
);
? "guest"
: null;
}
function hasCodeModeExecSurface(body: Record<string, unknown>) {
return resolveCodeModeExecSurface(body) !== null;
}
function resolveCurrentToolDeclarationSurface(
@@ -456,20 +468,70 @@ function parseToolCallArguments(toolCall: ResponsesInputItem) {
}
}
function isGeneratedCodeModeExecCall(toolCall: ResponsesInputItem | undefined) {
if (!toolCall || toolCall.name !== "exec") {
return false;
function readGeneratedCodeModeExecSource(toolCall: ResponsesInputItem | undefined) {
if (toolCall?.type === "custom_tool_call" && typeof toolCall.input === "string") {
return toolCall.input;
}
const args = parseToolCallArguments(toolCall);
return typeof args?.code === "string" && decodeCodeModeTarget(args.code) !== null;
const code = toolCall ? parseToolCallArguments(toolCall)?.code : undefined;
return typeof code === "string" ? code : undefined;
}
function isGeneratedCodeModeExecCall(toolCall: ResponsesInputItem | undefined) {
const source = toolCall?.name === "exec" ? readGeneratedCodeModeExecSource(toolCall) : undefined;
return typeof source === "string" && decodeCodeModeTarget(source) !== null;
}
function parseNativeCodeModeOutput(
output: unknown,
): { status: "waiting"; cellId: string } | { status: "completed"; value: unknown } | null {
if (!Array.isArray(output)) {
return null;
}
const readText = (item: unknown) =>
typeof item === "string"
? item
: item &&
typeof item === "object" &&
typeof (item as Record<string, unknown>).text === "string"
? String((item as Record<string, unknown>).text)
: null;
const statusText = readText(output[0]);
if (!statusText) {
return null;
}
const cellId = /^Script running with cell ID ([^\s\n]+)/u.exec(statusText)?.[1];
if (cellId) {
return { status: "waiting", cellId };
}
if (!statusText.startsWith("Script completed\n")) {
return null;
}
for (const item of output.slice(1).toReversed()) {
const text = readText(item);
if (!text) {
continue;
}
try {
return { status: "completed", value: JSON.parse(text) as unknown };
} catch {
// Native Code Mode may emit non-JSON content before the final value.
}
}
return null;
}
function isGeneratedCodeModeWaitCall(input: ResponsesInputItem[], toolCall: ResponsesInputItem) {
if (toolCall.name !== "wait") {
return false;
}
const runId = parseToolCallArguments(toolCall)?.runId;
if (typeof runId !== "string") {
const args = parseToolCallArguments(toolCall);
const waitId =
typeof args?.cell_id === "string"
? args.cell_id
: typeof args?.runId === "string"
? args.runId
: undefined;
if (!waitId) {
return false;
}
return input.some((item) => {
@@ -479,11 +541,12 @@ function isGeneratedCodeModeWaitCall(input: ResponsesInputItem[], toolCall: Resp
) {
return false;
}
const output = stringifyScenarioToolOutput(item.output);
const parsed = parseToolOutputJson(output);
const native = parseNativeCodeModeOutput(item.output);
const parsed = native ?? parseToolOutputJson(stringifyScenarioToolOutput(item.output));
return (
parsed?.status === "waiting" &&
parsed.runId === runId &&
(("cellId" in parsed && parsed.cellId === waitId) ||
("runId" in parsed && parsed.runId === waitId)) &&
isGeneratedCodeModeExecCall(findToolCallByCallId(input, item.call_id))
);
});
@@ -537,6 +600,23 @@ function buildScenarioToolCallEvents(
return buildRawToolCallEventsWithArgs(name, args, namespace);
}
const encodedTarget = encodeCodeModeTarget(name, args);
if (resolveCodeModeExecSurface(body) === "native") {
return buildCustomToolCallEventsWithInput(
"exec",
[
`// ${QA_CODE_MODE_TARGET_MARKER}${encodedTarget}`,
`const targetName = ${JSON.stringify(name)};`,
`const targetArgs = ${JSON.stringify(args)};`,
"const target = ALL_TOOLS.find((entry) => entry.name === targetName);",
"if (!target) throw new Error(`QA mock target tool unavailable: ${targetName}`);",
"let value = await tools[target.name](targetArgs);",
'if (targetName === "read" && value?.kind === "text" && typeof value.content === "string") {',
" value = { ...value, content: value.content.slice(0, 2048) };",
"}",
"text(JSON.stringify(value));",
].join("\n"),
);
}
return buildRawToolCallEventsWithArgs("exec", {
language: "javascript",
code: [
@@ -557,10 +637,16 @@ function buildScenarioToolCallEvents(
function extractScenarioPlannedTool(events: StreamEvent[]) {
const wireName = extractPlannedToolName(events);
const wireArgs = extractPlannedToolArgs(events);
if (wireName !== "exec" || typeof wireArgs?.code !== "string") {
const source =
typeof wireArgs?.input === "string"
? wireArgs.input
: typeof wireArgs?.code === "string"
? wireArgs.code
: undefined;
if (wireName !== "exec" || !source) {
return { name: wireName, args: wireArgs, wireName };
}
const target = decodeCodeModeTarget(wireArgs.code);
const target = decodeCodeModeTarget(source);
return target
? { name: target.name, args: target.args, wireName }
: { name: wireName, args: wireArgs, wireName };
@@ -701,19 +787,25 @@ async function buildResponsesPayload(
const prompt = extractLastUserText(input);
const hasCompletedToolOutput = hasToolOutput(input);
const rawToolOutput = extractToolOutput(input);
const codeModeControlJson = isCodeModeControlToolOutput(toolDeclarationBody, input)
? parseToolOutputJson(rawToolOutput)
const codeModeSurface = resolveCodeModeExecSurface(toolDeclarationBody);
const hasCodeModeControlOutput = isCodeModeControlToolOutput(toolDeclarationBody, input);
const codeModeControlJson = hasCodeModeControlOutput
? codeModeSurface === "native"
? parseNativeCodeModeOutput(extractToolOutputValue(input))
: parseToolOutputJson(rawToolOutput)
: null;
const toolOutput =
codeModeControlJson?.status === "completed" && Object.hasOwn(codeModeControlJson, "value")
? stringifyScenarioToolOutput(codeModeControlJson.value)
: rawToolOutput;
: codeModeSurface === "native" && hasCodeModeControlOutput
? ""
: rawToolOutput;
const completedToolCall = findToolCallByCallId(input, extractToolOutputCallId(input));
const completedToolName = (() => {
if (completedToolCall?.name !== "exec") {
return completedToolCall?.name;
}
const code = parseToolCallArguments(completedToolCall)?.code;
const code = readGeneratedCodeModeExecSource(completedToolCall);
return typeof code === "string" ? decodeCodeModeTarget(code)?.name : undefined;
})();
const buildToolCallEventsWithArgs = (name: string, args: Record<string, unknown>) =>
@@ -759,12 +851,13 @@ async function buildResponsesPayload(
? extractLatestToolOutput(input)
: "");
const toolJson = parseToolOutputJson(scenarioToolOutput);
if (
codeModeControlJson?.status === "waiting" &&
typeof codeModeControlJson.runId === "string" &&
hasToolDefinition(toolDeclarationBody, "wait")
) {
return buildRawToolCallEventsWithArgs("wait", { runId: codeModeControlJson.runId });
if (codeModeControlJson?.status === "waiting" && hasToolDefinition(toolDeclarationBody, "wait")) {
if ("cellId" in codeModeControlJson && typeof codeModeControlJson.cellId === "string") {
return buildRawToolCallEventsWithArgs("wait", { cell_id: codeModeControlJson.cellId });
}
if ("runId" in codeModeControlJson && typeof codeModeControlJson.runId === "string") {
return buildRawToolCallEventsWithArgs("wait", { runId: codeModeControlJson.runId });
}
}
if (compactionRetryScenarioActive) {
if (isCanonicalCompactionRetryWriteResult(toolOutput)) {
@@ -1886,40 +1979,68 @@ async function buildResponsesPayload(
return buildAssistantEvents("NONE");
}
if (/thread memory check/i.test(allInputText)) {
if (!scenarioToolOutput) {
if (!hasCompletedToolOutput) {
return buildToolCallEventsWithArgs("memory_search", {
query: "hidden thread codename ORBIT-22",
maxResults: 3,
});
}
if (memoryToolUnavailable) {
const directThreadMemoryJson =
Array.isArray(toolJson?.results) || typeof toolJson?.text === "string" ? toolJson : null;
const completedMemoryValue =
toolJson?.status === "completed" &&
(completedToolName === "memory_search" || completedToolName === "memory_get") &&
toolJson.value !== null &&
typeof toolJson.value === "object" &&
!Array.isArray(toolJson.value)
? (toolJson.value as Record<string, unknown>)
: null;
const threadMemoryJson = completedMemoryValue ?? directThreadMemoryJson;
const threadMemoryToolName = completedMemoryValue
? completedToolName
: Array.isArray(threadMemoryJson?.results)
? "memory_search"
: typeof threadMemoryJson?.text === "string"
? "memory_get"
: undefined;
const threadMemoryUnavailable =
threadMemoryJson?.unavailable === true ||
threadMemoryJson?.disabled === true ||
(typeof threadMemoryJson?.error === "string" && threadMemoryJson.error.trim().length > 0);
if (threadMemoryUnavailable) {
return buildAssistantEvents("NONE");
}
const transcriptOrbitCode = extractOrbitCode(scenarioToolOutput);
if (transcriptOrbitCode) {
return buildAssistantEvents(
`Protocol note: I checked memory in-thread and the hidden thread codename is ${transcriptOrbitCode}.`,
);
if (threadMemoryToolName === "memory_search") {
const results = Array.isArray(threadMemoryJson?.results)
? (threadMemoryJson.results as Array<Record<string, unknown>>)
: [];
const first = results[0];
if (
typeof first?.path === "string" &&
(typeof first.startLine === "number" || typeof first.endLine === "number")
) {
const from =
typeof first.startLine === "number"
? Math.max(1, first.startLine)
: typeof first.endLine === "number"
? Math.max(1, first.endLine)
: 1;
return buildToolCallEventsWithArgs("memory_get", {
path: first.path,
from,
lines: 4,
});
}
}
const results = Array.isArray(toolJson?.results)
? (toolJson.results as Array<Record<string, unknown>>)
: [];
const first = results[0];
if (
typeof first?.path === "string" &&
(typeof first.startLine === "number" || typeof first.endLine === "number")
) {
const from =
typeof first.startLine === "number"
? Math.max(1, first.startLine)
: typeof first.endLine === "number"
? Math.max(1, first.endLine)
: 1;
return buildToolCallEventsWithArgs("memory_get", {
path: first.path,
from,
lines: 4,
});
const memoryGetText =
threadMemoryToolName === "memory_get" && typeof threadMemoryJson?.text === "string"
? threadMemoryJson.text
: "";
const memoryGetOrbitCode = extractOrbitCode(memoryGetText);
if (memoryGetOrbitCode) {
return buildAssistantEvents(
`Protocol note: I checked memory in-thread and the hidden thread codename is ${memoryGetOrbitCode}.`,
);
}
return buildAssistantEvents("NONE");
}
@@ -2185,13 +2306,7 @@ export async function startQaMockOpenAiServer(params?: {
const scenarioStates = new Map<string, MockScenarioState>();
const servedCompactionSummaryFaultMarkers = new Set<string>();
const scenarioStateFor = (body: Record<string, unknown>): MockScenarioState => {
const input =
typeof body.input === "string" || Array.isArray(body.input)
? normalizeResponsesInput(body.input)
: convertAnthropicMessagesToResponsesInput({
system: body.system as AnthropicMessagesRequest["system"],
messages: [],
});
const input = normalizeResponsesInput(body.input);
const sessionId =
resolveQaRuntimeSessionId(input, body) ??
(body.client_metadata as { session_id?: unknown } | undefined)?.session_id;
@@ -2224,18 +2339,25 @@ export async function startQaMockOpenAiServer(params?: {
const inflightRequests = new Map<number, { prompt: string; allInputText: string }>();
let nextInflightRequestId = 1;
const imageGenerationRequests: Array<Record<string, unknown>> = [];
const dispatchResponses = async (request: {
body: Record<string, unknown>;
raw: string;
}): Promise<QaMockResponsesDispatchResult> => {
const input = normalizeResponsesInput(request.body.input);
const dispatchProvider = async (
request: QaMockProviderDispatchRequest,
): Promise<QaMockProviderDispatchResult> => {
const normalized =
request.route === "anthropic-messages"
? normalizeAnthropicMessagesRequest(request.body as AnthropicMessagesRequest)
: {
body: request.body,
input: normalizeResponsesInput(request.body.input),
model: typeof request.body.model === "string" ? request.body.model : "",
};
const { body, input, model } = normalized;
if (isRemoteCompactionV2Request(input)) {
return { events: buildRemoteCompactionV2Events() };
return { events: buildRemoteCompactionV2Events(), model };
}
const prompt = extractLastUserText(input);
const allInputText = extractAllRequestTexts(input, request.body);
const scenarioState = scenarioStateFor(request.body);
const requestKind = classifyMockOpenAiRequest(input, request.body);
const allInputText = extractAllRequestTexts(input, body);
const scenarioState = scenarioStateFor(body);
const requestKind = classifyMockOpenAiRequest(input, body);
const compactionSummaryFaultMode = resolveCompactionSummaryFaultMode({
allInputText,
requestKind,
@@ -2248,16 +2370,15 @@ export async function startQaMockOpenAiServer(params?: {
const compactionOverflowThresholdBytes = hasCompactionOutputRecoveryMarker(allInputText)
? QA_COMPACTION_OUTPUT_RECOVERY_OVERFLOW_THRESHOLD_BYTES
: QA_COMPACTION_RETRY_OVERFLOW_THRESHOLD_BYTES;
const resolvedModel = typeof request.body.model === "string" ? request.body.model : "";
const requestSnapshotBase = {
raw: request.raw,
body: request.body,
body,
prompt,
allInputText,
instructions: extractInstructionsText(request.body) || undefined,
instructions: extractInstructionsText(body) || undefined,
toolOutput: extractToolOutput(input),
model: resolvedModel,
providerVariant: resolveProviderVariant(resolvedModel),
model,
providerVariant: resolveProviderVariant(model),
imageInputCount: countImageInputs(input),
requestKind,
compactionSummaryFaultMode,
@@ -2267,6 +2388,7 @@ export async function startQaMockOpenAiServer(params?: {
| "outcome"
| "errorCode"
| "plannedToolCallId"
| "plannedToolItemId"
| "plannedToolName"
| "plannedWireToolName"
| "plannedToolArgs"
@@ -2288,6 +2410,7 @@ export async function startQaMockOpenAiServer(params?: {
});
return {
events: [],
model,
failure: {
status: 400,
type: "invalid_request_error",
@@ -2299,15 +2422,48 @@ export async function startQaMockOpenAiServer(params?: {
const inflightRequestId = nextInflightRequestId++;
inflightRequests.set(inflightRequestId, { prompt, allInputText });
let events: StreamEvent[];
let injectedFailure: QaMockProviderDispatchResult["failure"];
try {
events = await buildResponsesPayload(request.body, scenarioState, {
waitForTerminalRequesterSettled: terminalRequesterSettleGate.waitUntilSettled,
requestKind,
compactionSummaryFaultMode,
});
if (
request.route === "anthropic-messages" &&
QA_ANTHROPIC_THINKING_ERROR_RECOVERY_PROMPT_RE.test(allInputText)
) {
const toolOutput = extractToolOutput(input);
const toolOutputCallId = extractToolOutputCallId(input);
const scenarioKey = `${model}\n${extractLastUserText(input)}`;
const shouldFail =
toolOutput.length > 0 &&
toolOutputCallId.length > 0 &&
!scenarioState.anthropicThinkingErrorScenarioKeys.has(scenarioKey);
if (shouldFail) {
scenarioState.anthropicThinkingErrorScenarioKeys.add(scenarioKey);
injectedFailure = {
status: 200,
type: "api_error",
message: "QA injected provider stream failure",
presentation: "anthropic-thinking",
};
}
events =
toolOutput.length === 0
? buildRawToolCallEventsWithArgs("read", { path: "QA_KICKOFF_TASK.md" })
: shouldFail
? buildAssistantEvents("")
: buildAssistantEvents("ANTHROPIC-THINKING-ERROR-RECOVERED-OK");
} else {
events = await buildResponsesPayload(body, scenarioState, {
waitForTerminalRequesterSettled: terminalRequesterSettleGate.waitUntilSettled,
requestKind,
compactionSummaryFaultMode,
});
}
} finally {
inflightRequests.delete(inflightRequestId);
}
if (request.route === "anthropic-messages") {
events = adaptAnthropicToolCallIds(events);
}
const plannedToolIdentity = extractPlannedToolIdentity(events);
const plannedTool = extractScenarioPlannedTool(events);
const terminalRequesterCase = extractLastMatchingUserTurn(
input,
@@ -2316,7 +2472,7 @@ export async function startQaMockOpenAiServer(params?: {
?.text.match(QA_SUBAGENT_TERMINAL_MATRIX_PROMPT_RE)?.[1]
?.toLowerCase();
const settledTerminalRequester =
terminalRequesterCase && resolveQaRuntimeSessionId(input, request.body)
terminalRequesterCase && resolveQaRuntimeSessionId(input, body)
? {
caseName: terminalRequesterCase,
childSessionKey: resolveAcceptedChildSessionKey(input),
@@ -2325,17 +2481,21 @@ export async function startQaMockOpenAiServer(params?: {
const settledTerminalCaseName = settledTerminalRequester?.caseName;
const settledChildSessionKey = settledTerminalRequester?.childSessionKey;
const failure =
QA_PROVIDER_HTTP_503_AFTER_TOOL_PROMPT_RE.test(allInputText) && hasToolOutput(input)
injectedFailure ??
(QA_PROVIDER_HTTP_503_AFTER_TOOL_PROMPT_RE.test(allInputText) && hasToolOutput(input)
? {
status: 503,
type: "server_error",
message: "Service Unavailable",
}
: undefined;
: undefined);
recordRequest({
...requestSnapshotBase,
outcome: failure ? "error" : "success",
plannedToolCallId: extractPlannedToolCallId(events),
plannedToolCallId: plannedToolIdentity.callId,
...(request.route === "responses" && plannedToolIdentity.itemId
? { plannedToolItemId: plannedToolIdentity.itemId }
: {}),
plannedToolName: plannedTool.name,
...(plannedTool.wireName && plannedTool.wireName !== plannedTool.name
? { plannedWireToolName: plannedTool.wireName }
@@ -2346,6 +2506,7 @@ export async function startQaMockOpenAiServer(params?: {
});
return {
events,
model,
...(settledTerminalCaseName && settledChildSessionKey
? {
onResponseSent: () =>
@@ -2361,6 +2522,8 @@ export async function startQaMockOpenAiServer(params?: {
: {}),
};
};
const dispatchResponses = (request: Omit<QaMockProviderDispatchRequest, "route">) =>
dispatchProvider({ ...request, route: "responses" });
const server = createServer((req, res) => {
void (async () => {
const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -2492,16 +2655,6 @@ export async function startQaMockOpenAiServer(params?: {
writeOpenAiMalformedJsonError(res, "OpenAI Responses");
return;
}
const input = normalizeResponsesInput(body.input);
if (isRemoteCompactionV2Request(input)) {
const events = buildRemoteCompactionV2Events();
if (body.stream === false) {
writeJson(res, 200, events[1].response);
} else {
writeSse(res, events);
}
return;
}
const dispatched = await dispatchResponses({ body, raw });
if (dispatched.failure) {
writeJson(res, dispatched.failure.status, {
@@ -2545,48 +2698,25 @@ export async function startQaMockOpenAiServer(params?: {
});
return;
}
const scenarioState = scenarioStateFor(body as Record<string, unknown>);
const {
events,
input,
responseBody,
streamEvents,
model: normalizedModel,
} = await buildMessagesPayload(body, scenarioState, buildResponsesPayload);
const plannedTool = extractScenarioPlannedTool(events);
// Record the adapted request snapshot so /debug/requests gives the QA
// suite the same plannedToolName / allInputText / toolOutput signals
// on the Anthropic route that the OpenAI route already exposes. This
// is what lets a single parity run diff assertions across both lanes.
// Reuse the normalized model so an empty-string body.model no longer
// leaks through to `lastRequest.model`.
recordRequest({
raw,
const dispatched = await dispatchProvider({
route: "anthropic-messages",
body: body as Record<string, unknown>,
prompt: extractLastUserText(input),
allInputText: extractAllInputTexts(input),
toolOutput: extractToolOutput(input),
model: normalizedModel,
providerVariant: resolveProviderVariant(normalizedModel),
imageInputCount: countImageInputs(input),
requestKind: classifyMockOpenAiRequest(input, body as Record<string, unknown>),
compactionSummaryFaultMode: "none",
outcome: "success",
rawByteLength: Buffer.byteLength(raw),
plannedToolCallId: extractPlannedToolCallId(events),
plannedToolName: plannedTool.name,
...(plannedTool.wireName && plannedTool.wireName !== plannedTool.name
? { plannedWireToolName: plannedTool.wireName }
: {}),
plannedToolArgs: plannedTool.args,
toolOutputCallId: extractToolOutputCallId(input) || undefined,
...(extractToolOutputStructuredError(input) ? { toolOutputStructuredError: true } : {}),
raw,
});
const { responseBody, streamEvents } = buildMessagesPayload(dispatched);
if (dispatched.failure?.presentation !== "anthropic-thinking") {
if (dispatched.failure) {
writeJson(res, dispatched.failure.status, responseBody);
return;
}
}
if (body.stream === true) {
writeAnthropicSse(res, streamEvents);
dispatched.onResponseSent?.();
return;
}
writeJson(res, 200, responseBody);
writeJson(res, dispatched.failure?.status ?? 200, responseBody);
dispatched.onResponseSent?.();
return;
}
writeJson(res, 404, { error: "not found" });
@@ -105,7 +105,7 @@ describe("buildQaGatewayConfig", () => {
expect(cfg.models?.providers?.anthropic?.baseUrl).toBe("http://127.0.0.1:44080");
expect(cfg.models?.providers?.anthropic?.request).toEqual({ allowPrivateNetwork: true });
expect(cfg.memory?.search).toMatchObject({
provider: "openai",
provider: "openai-compatible",
model: "text-embedding-3-small",
remote: {
baseUrl: "http://127.0.0.1:44080/v1",
+1 -1
View File
@@ -202,7 +202,7 @@ export function buildQaGatewayConfig(params: {
const mockMemorySearch =
provider.kind === "mock"
? {
provider: "openai",
provider: "openai-compatible",
model: "text-embedding-3-small",
remote: {
// Memory embeddings bypass the model runtime, so bind them to the
@@ -0,0 +1,215 @@
import { describe, expect, it } from "vitest";
import { readQaScenarioById, readQaScenarioExecutionConfig } from "./scenario-catalog.js";
import { readFlowAssertExpression, requireFlowScenario } from "./scenario-catalog.test-utils.js";
describe("qa scenario catalog causality", () => {
it("loads live gateway sentinel scenarios for harness self-health", () => {
const scenarioIds = [
"plugin-hook-health-sentinel",
"plugin-manifest-contract-health",
"webchat-direct-reply-routing",
"long-context-progress-watchdog",
"gateway-restart-inflight-run",
"gateway-restart-multi-live",
"streaming-final-integrity",
];
for (const scenarioId of scenarioIds) {
const scenario = readQaScenarioById(scenarioId);
expect(scenario.execution.flow?.steps.length).toBeGreaterThan(0);
expect(scenario.coverage?.primary.length).toBeGreaterThan(0);
}
expect(readQaScenarioById("webchat-direct-reply-routing").sourcePath).toBe(
"qa/scenarios/channels/webchat-direct-reply-routing.yaml",
);
expect(readQaScenarioById("long-context-progress-watchdog").sourcePath).toBe(
"qa/scenarios/runtime/long-context-progress-watchdog.yaml",
);
const gatewayRestart = requireFlowScenario(readQaScenarioById("gateway-restart-inflight-run"));
const gatewayRestartFlow = gatewayRestart.execution.flow;
const gatewayRestartContract = JSON.stringify(gatewayRestartFlow);
const gatewayRestartActions = gatewayRestartFlow?.steps[0]?.actions ?? [];
const recoveryPollIndex = gatewayRestartActions.findIndex(
(action) =>
(action as { call?: string }).call === "waitForCondition" &&
(action as { saveAs?: string }).saveAs === "settledRecovery",
);
const outboundIndex = gatewayRestartActions.findIndex(
(action) =>
(action as { call?: string }).call === "waitForOutboundMessage" &&
(action as { saveAs?: string }).saveAs === "outbound",
);
const preOutboundRecoveryAssertIndex = gatewayRestartActions.findIndex((action) =>
readFlowAssertExpression(action).includes(
"restartRecoveryRequestsBeforeOutbound.length === 1",
),
);
const preOutboundHeartbeatAssertIndex = gatewayRestartActions.findIndex((action) =>
readFlowAssertExpression(action).includes(
"!String(restartRecoveryRequestsBeforeOutbound[0].prompt ?? '').includes('[OpenClaw heartbeat poll]')",
),
);
const settledRequestsIndex = gatewayRestartActions.findIndex(
(action) => (action as { set?: string }).set === "settledRecoveryRequests",
);
const settledDedupeAssertIndex = gatewayRestartActions.findIndex((action) =>
readFlowAssertExpression(action).includes("settledRestartRecoveryRequests.length === 1"),
);
const recoveryPoll = gatewayRestartActions[recoveryPollIndex] as
| { args?: Array<{ lambda?: { expr?: string } }> }
| undefined;
const recoveryPollExpr = recoveryPoll?.args?.[0]?.lambda?.expr ?? "";
expect(gatewayRestart.execution.retryCount).toBe(0);
expect(JSON.stringify(gatewayRestart.gatewayConfigPatch)).toContain(
'"alsoAllow":["qa_restart_wait","qa_restart_unsafe_probe"]',
);
expect(gatewayRestartContract).toContain("plannedToolName === 'wait'");
expect(gatewayRestartContract).toContain("lastAssistantToolNames?.includes('wait')");
expect(gatewayRestartContract).toContain("restartRecoveryDeliveryContext");
expect(gatewayRestartContract).toContain("sendInbound");
expect(gatewayRestartContract).not.toContain("startAgentRun");
expect(gatewayRestartContract).toContain('"restartGatewayWithConfigPatch"');
expect(gatewayRestartContract).toContain("interruptedMatches.length === 1");
expect(gatewayRestartContract).toContain("restartNotices.length === 0");
expect(gatewayRestartContract).toContain("dispatching restart-safe recovery");
expect(recoveryPollIndex).toBeGreaterThanOrEqual(0);
expect(recoveryPollExpr).toContain(
"String(request.prompt ?? '').includes('Your previous turn was interrupted by a gateway restart')",
);
expect(recoveryPollExpr).toContain(
"String(request.allInputText ?? '').includes(config.interruptedMarker)",
);
expect(recoveryPollExpr).toContain("restartRecoveryRequests.length >= 1");
expect(recoveryPollExpr).toContain(
"String(transcript.finalText ?? '').includes(config.interruptedMarker)",
);
expect(preOutboundRecoveryAssertIndex).toBeGreaterThan(recoveryPollIndex);
expect(preOutboundHeartbeatAssertIndex).toBeGreaterThan(preOutboundRecoveryAssertIndex);
expect(outboundIndex).toBeGreaterThan(preOutboundHeartbeatAssertIndex);
expect(settledRequestsIndex).toBeGreaterThan(outboundIndex);
expect(settledDedupeAssertIndex).toBeGreaterThan(settledRequestsIndex);
expect(
gatewayRestartActions.some((action) => (action as { call?: string }).call === "sleep"),
).toBe(false);
expect(gatewayRestartContract).toContain("recoveryPromptHeartbeat=false");
expect(gatewayRestartContract).toContain("liveTurnTimeoutMs(env, 180000)");
expect(gatewayRestartContract).toContain("id: `dm:${conversationId}`");
expect(gatewayRestartContract).toContain("dmScope: env.cfg.session?.dmScope");
expect(gatewayRestart.gatewayConfigPatch).toMatchObject({
plugins: {
slots: { memory: "none" },
entries: {
acpx: { enabled: false },
"memory-core": { enabled: false },
},
},
});
const liveMultiRestart = readQaScenarioById("gateway-restart-multi-live");
const liveMultiRestartContract = JSON.stringify(liveMultiRestart.execution.flow);
expect(JSON.stringify(liveMultiRestart.gatewayConfigPatch)).toContain(
'"alsoAllow":["qa_restart_wait","qa_restart_unsafe_probe"]',
);
expect(liveMultiRestartContract).toContain("assistantToolCallCounts.exec");
expect(liveMultiRestartContract).toContain("checkpoint");
expect(liveMultiRestartContract).toContain("restarts=3");
expect(liveMultiRestartContract).toContain("dmScope: 'per-channel-peer'");
expect(liveMultiRestartContract).toContain("dispatching restart-safe recovery");
expect(readQaScenarioExecutionConfig("gateway-restart-multi-live")).toMatchObject({
requiredProviderMode: "live-frontier",
requiredProvider: "openai",
requiredModel: "gpt-5.4",
});
});
it("scopes prompt diagnostics to requests after each scenario cursor", () => {
for (const scenarioId of [
"instruction-followthrough-repo-contract",
"subagent-handoff",
] as const) {
const scenario = requireFlowScenario(readQaScenarioById(scenarioId));
const flow = JSON.stringify(scenario.execution.flow);
const cursorIndex = flow.indexOf("/debug/request-cursor");
const promptIndex = flow.indexOf('"call":"runAgentPrompt"');
const requestsIndex = flow.indexOf("/debug/requests?after=${requestCursorBefore}");
expect(cursorIndex, scenarioId).toBeGreaterThanOrEqual(0);
expect(cursorIndex, scenarioId).toBeLessThan(promptIndex);
expect(requestsIndex, scenarioId).toBeGreaterThan(promptIndex);
expect(flow, scenarioId).not.toContain("`${env.mock.baseUrl}/debug/requests`");
}
});
it.each([
[
"thread-memory-isolation",
"poll",
"finalRequest.toolOutputCallId === searchResultRequest.plannedToolCallId",
],
[
"memory-tools-channel-context",
"poll",
"finalRequest.toolOutputCallId === searchResultRequest.plannedToolCallId",
],
[
"agent-tool-consumption",
"immediate",
"getResultRequest.toolOutputCallId === searchResultRequest.plannedToolCallId",
],
] as const)(
"asserts the complete memory tool chain before %s delivery",
(scenarioId, requestCollectionMode, finalLinkNeedle) => {
const scenario = requireFlowScenario(readQaScenarioById(scenarioId));
const actions = scenario.execution.flow?.steps[0]?.actions ?? [];
const outboundIndex = actions.findIndex(
(action) => (action as { call?: string }).call === "waitForOutboundMessage",
);
const requestCollectionIndex = actions.findIndex((action) =>
requestCollectionMode === "poll"
? (action as { call?: string }).call === "waitForCondition" &&
(action as { saveAs?: string }).saveAs === "scenarioRequests"
: (action as { set?: string }).set === "scenarioRequests",
);
const requestCountAssertIndex = actions.findIndex((action) =>
readFlowAssertExpression(action).includes("scenarioRequests.length === 3"),
);
const searchPlanAssertIndex = actions.findIndex((action) =>
readFlowAssertExpression(action).includes(
"searchPlanRequest.plannedToolName === 'memory_search'",
),
);
const searchResultAssertIndex = actions.findIndex((action) =>
readFlowAssertExpression(action).includes(
"searchResultRequest.toolOutputCallId === searchPlanRequest.plannedToolCallId",
),
);
const finalRequestAssertIndex = actions.findIndex((action) =>
readFlowAssertExpression(action).includes(finalLinkNeedle),
);
expect(requestCollectionIndex, scenarioId).toBeGreaterThanOrEqual(0);
expect(requestCountAssertIndex, scenarioId).toBeGreaterThan(requestCollectionIndex);
expect(searchPlanAssertIndex, scenarioId).toBeGreaterThan(requestCountAssertIndex);
expect(searchResultAssertIndex, scenarioId).toBeGreaterThan(searchPlanAssertIndex);
expect(finalRequestAssertIndex, scenarioId).toBeGreaterThan(searchResultAssertIndex);
expect(outboundIndex, scenarioId).toBeGreaterThan(finalRequestAssertIndex);
if (requestCollectionMode === "poll") {
const requestPoll = actions[requestCollectionIndex] as
| { args?: Array<{ lambda?: { expr?: string } }> }
| undefined;
expect(requestPoll?.args?.[0]?.lambda?.expr, scenarioId).toContain(
"requests.length >= 3 ? requests : undefined",
);
} else {
expect(
actions.some(
(action) =>
(action as { call?: string }).call === "waitForCondition" &&
(action as { saveAs?: string }).saveAs === "scenarioRequests",
),
scenarioId,
).toBe(false);
}
},
);
});
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { readQaScenarioById } from "./scenario-catalog.js";
import { requireFlowScenario } from "./scenario-catalog.test-utils.js";
import { readFlowAssertExpression, requireFlowScenario } from "./scenario-catalog.test-utils.js";
describe("qa compaction scenario catalog", () => {
it.each([
@@ -56,6 +56,47 @@ describe("qa compaction scenario catalog", () => {
const scenario = requireFlowScenario(readQaScenarioById("compaction-retry-mutating-tool"));
const flow = JSON.stringify(scenario.execution.flow);
const serializedScenario = JSON.stringify(scenario);
const actions = scenario.execution.flow?.steps[0]?.actions ?? [];
const readSetExpression = (name: string) => {
const action = actions.find((candidate) => (candidate as { set?: string }).set === name) as
| { value?: { expr?: string } }
| undefined;
return action?.value?.expr ?? "";
};
const readAssertExpression = (needle: string) =>
actions.map(readFlowAssertExpression).find((expression) => expression.includes(needle)) ?? "";
const actionIndex = (predicate: (action: (typeof actions)[number]) => boolean) =>
actions.findIndex(predicate);
const writeRequestsExpr = readSetExpression("writeRequests");
const postWriteContinuationsExpr = readSetExpression("postWriteContinuations");
const writeTranscriptToolCallIdExpr = readSetExpression("writeTranscriptToolCallId");
const continuationChainExpr = readSetExpression("continuationChain");
const continuationAssertIndex = actionIndex((action) =>
readFlowAssertExpression(action).includes("continuationChain.valid === true"),
);
const terminalAssertIndex = actionIndex((action) =>
readFlowAssertExpression(action).includes("terminalContinuations.length === 1"),
);
const distinctCallIdsAssertIndex = actionIndex((action) =>
readFlowAssertExpression(action).includes("new Set([writeRequest.plannedToolCallId"),
);
const stableCellIdAssertIndex = actionIndex((action) =>
readFlowAssertExpression(action).includes("continuationChain.waits.length === 0"),
);
const terminalEvidenceAssertIndex = actionIndex((action) =>
readFlowAssertExpression(action).includes(
"terminalContinuations[0].providerVariant === 'openai'",
),
);
const outboundWaitIndex = actionIndex(
(action) =>
(action as { call?: string }).call === "waitForCondition" &&
(action as { saveAs?: string }).saveAs === "outbound",
);
const stableCellIdAssertExpr = readAssertExpression("continuationChain.waits.length === 0");
const terminalEvidenceAssertExpr = readAssertExpression(
"terminalContinuations[0].providerVariant === 'openai'",
);
const knownGap =
"known-harness-gap compaction-retry-mutating-tool: provider-error recovery does not invoke Codex native compaction; native token-threshold compaction needs a separate scenario.";
@@ -70,7 +111,7 @@ describe("qa compaction scenario catalog", () => {
"One coded over-threshold provider overflow produces one persisted OpenClaw overflow compaction and one compacted retry retaining durable current context.",
);
expect(scenario.successCriteria).toContain(
"OpenClaw performs exactly one successful write, one causal continuation, and returns the exact file content and final marker.",
"OpenClaw performs exactly one successful write, then one terminal continuation after zero-or-more causally linked waits, and returns the exact file content and final marker.",
);
expect(scenario.successCriteria).toContain(
"OpenClaw proves session-memory.pruning by retaining a nonempty contiguous suffix ending at block 15 while pruning marker block 10.",
@@ -114,31 +155,129 @@ describe("qa compaction scenario catalog", () => {
expect(flow).toContain("seedQaSessionTranscript");
expect(flow).toContain("sessions.compaction.branch");
expect(flow).toContain("env.runtimeId");
expect(flow).toContain('"transcriptToolName":"write"');
expect(flow).toContain('"requireSuccessfulTranscriptToolResult":true');
expect(scenario.execution.retryCount).toBe(0);
expect(flow).not.toContain('"transcriptToolName":"write"');
expect(flow).not.toContain('"requireSuccessfulTranscriptToolResult":true');
expect(serializedScenario).not.toContain("expectedWriteToolResult");
expect(flow).toContain("outbound.text === config.finalMarker");
expect(flow).toContain("overflowRequests.length === 1");
expect(flow).toContain("overflowRequest.rawByteLength > config.overflowThresholdBytes");
expect(flow).toContain("writeRequests.length === 1");
expect(flow).toContain("String(request.allInputText ?? '').includes(sessionId)");
expect(writeRequestsExpr).toContain("request.plannedToolName === 'write'");
expect(writeRequestsExpr).toContain("request.cursor > overflowRequest.cursor");
expect(writeRequestsExpr).toContain("String(request.allInputText ?? '').includes(sessionId)");
expect(writeRequestsExpr).toContain(
"String(request.allInputText ?? '').includes(config.promptSnippet)",
);
expect(writeRequestsExpr).toContain(
"String(request.allInputText ?? '').includes(config.durableMarker)",
);
expect(writeRequestsExpr).not.toContain("request.requestKind");
expect(writeRequestsExpr).not.toContain("request.outcome");
expect(writeRequestsExpr).not.toContain("request.toolOutput");
expect(writeRequestsExpr).not.toContain("request.plannedToolArgs");
expect(writeRequestsExpr).not.toContain("request.plannedWireToolName");
expect(flow).toContain("writeRequest.requestKind === 'agent-initial'");
expect(flow).toContain("writeRequest.outcome === 'success'");
expect(flow).toContain("!writeRequest.toolOutput");
expect(flow).toContain(
"String(writeRequest.allInputText ?? '').includes(config.durableMarker)",
);
expect(flow).toContain("request.cursor > overflowRequest.cursor");
expect(flow).toContain("request.plannedToolArgs?.path === config.outputFile");
expect(flow).toContain("request.plannedToolArgs?.content === config.expectedFileContent");
expect(flow).toContain("request.toolOutputCallId === writeRequest.plannedToolCallId");
expect(flow).toContain("request.cursor > writeRequest.cursor");
expect(flow).not.toContain("request.plannedWireToolName === 'exec'");
expect(flow).toContain(
"String(request.toolOutput ?? '').trim() === config.expectedWriteToolResult",
"writeRequest.plannedWireToolName === undefined || writeRequest.plannedWireToolName === 'exec'",
);
expect(flow).toContain("writeRequest.plannedToolArgs?.path === config.outputFile");
expect(flow).toContain("writeRequest.plannedToolArgs?.content === config.expectedFileContent");
expect(flow).toContain("typeof writeRequest.plannedToolCallId === 'string'");
expect(flow).not.toContain(
"writeRequest.plannedToolCallId.length > 0 && typeof writeRequest.plannedToolItemId",
);
expect(flow).toContain(
'writeWireToolName","value":{"expr":"writeRequest.plannedWireToolName ?? writeRequest.plannedToolName',
);
expect(writeTranscriptToolCallIdExpr).toContain(
"typeof writeRequest.plannedToolItemId === 'string'",
);
expect(writeTranscriptToolCallIdExpr).toContain("writeRequest.plannedToolItemId.length > 0");
expect(writeTranscriptToolCallIdExpr).toContain(
"`${writeRequest.plannedToolCallId}|${writeRequest.plannedToolItemId}`",
);
expect(writeTranscriptToolCallIdExpr).toContain(": writeRequest.plannedToolCallId");
expect(flow).toContain(
"event.toolCallId === writeTranscriptToolCallId && event.name === writeWireToolName",
);
expect(flow).toContain("successfulWriteTranscriptEvents.length === 1");
expect(flow).toContain("transcript.successfulToolCallCounts[writeWireToolName] === 1");
expect(flow).not.toContain("transcript.successfulToolCallCounts.write === 1");
expect(postWriteContinuationsExpr).toContain("request.requestKind === 'tool-continuation'");
expect(postWriteContinuationsExpr).toContain("request.cursor > writeRequest.cursor");
expect(postWriteContinuationsExpr).toContain(
"String(request.allInputText ?? '').includes(sessionId)",
);
expect(postWriteContinuationsExpr).not.toContain("request.outcome");
expect(postWriteContinuationsExpr).not.toContain("request.plannedToolName");
expect(postWriteContinuationsExpr).not.toContain("request.toolOutputCallId");
expect(postWriteContinuationsExpr).not.toContain("request.toolOutputStructuredError");
expect(flow).toContain("let currentCallId = writeRequest.plannedToolCallId");
expect(flow).toContain("postWriteContinuations.filter");
expect(flow).toContain("request.toolOutputCallId === currentCallId");
expect(flow).toContain("request.plannedToolName === 'wait'");
expect(flow).toContain("currentCallId = request.plannedToolCallId");
expect(continuationChainExpr).toContain("request.toolOutputCallId === currentCallId");
expect(continuationChainExpr).toContain("request.plannedToolName === 'wait'");
expect(flow).toContain("continuationChain.requests.length === postWriteContinuations.length");
expect(flow).toContain(
"continuationChain.requests.every((request, index) => request === postWriteContinuations[index])",
);
expect(flow).toContain(
"continuationChain.requests.length === continuationChain.waits.length + 1",
);
expect(flow).toContain("request.outcome === 'success'");
expect(flow).toContain("request.toolOutputStructuredError !== true");
expect(flow).toContain("terminalContinuations.length === 1");
expect(flow).toContain("terminalContinuations[0] === continuationChain.terminal");
expect(flow).toContain("String(terminalContinuations[0].toolOutput ?? '').trim().length > 0");
expect(flow).toContain("new Set([writeRequest.plannedToolCallId");
expect(stableCellIdAssertExpr).toContain("continuationChain.waits.length === 0 ||");
expect(stableCellIdAssertExpr).toContain(
"typeof request.plannedToolArgs?.cell_id === 'string'",
);
expect(stableCellIdAssertExpr).toContain(
"new Set(continuationChain.waits.map((request) => request.plannedToolArgs.cell_id)).size === 1",
);
expect(terminalEvidenceAssertExpr).toContain("writeWireToolName !== 'exec'");
const openAiEvidenceIndex = terminalEvidenceAssertExpr.indexOf(
"terminalContinuations[0].providerVariant === 'openai'",
);
const anthropicEvidenceIndex = terminalEvidenceAssertExpr.indexOf(
"terminalContinuations[0].providerVariant === 'anthropic'",
);
const unknownProviderFailClosedIndex = terminalEvidenceAssertExpr.lastIndexOf(": false");
expect(openAiEvidenceIndex).toBeGreaterThanOrEqual(0);
expect(terminalEvidenceAssertExpr).toContain("startsWith('Script completed\\n')");
expect(anthropicEvidenceIndex).toBeGreaterThan(openAiEvidenceIndex);
expect(terminalEvidenceAssertExpr).toContain(
"JSON.parse(String(terminalContinuations[0].toolOutput ?? ''))",
);
expect(terminalEvidenceAssertExpr).toContain("parsed !== null");
expect(terminalEvidenceAssertExpr).toContain("typeof parsed === 'object'");
expect(terminalEvidenceAssertExpr).toContain("!Array.isArray(parsed)");
expect(terminalEvidenceAssertExpr).toContain("parsed.status === 'completed'");
expect(unknownProviderFailClosedIndex).toBeGreaterThan(anthropicEvidenceIndex);
expect(continuationAssertIndex).toBeGreaterThanOrEqual(0);
expect(terminalAssertIndex).toBeGreaterThan(continuationAssertIndex);
expect(distinctCallIdsAssertIndex).toBeGreaterThan(terminalAssertIndex);
expect(stableCellIdAssertIndex).toBeGreaterThan(distinctCallIdsAssertIndex);
expect(terminalEvidenceAssertIndex).toBeGreaterThan(stableCellIdAssertIndex);
expect(outboundWaitIndex).toBeGreaterThan(terminalEvidenceAssertIndex);
expect(flow).not.toContain("config.expectedOpenClawToolResult");
expect(flow).not.toContain("String(request.toolOutput ?? '').includes(`---");
expect(flow).not.toContain("String(request.toolOutput ?? '').includes(`+++");
expect(flow).toContain("postWriteContinuations.length === 1");
expect(flow).toContain(
"compactionSummaryRequests.every((request) => request.outcome === 'success' && request.plannedToolName === undefined)",
"compactionSummaryRequests.length === 1 && compactionSummaryRequests[0].outcome === 'success' && compactionSummaryRequests[0].plannedToolName === undefined && compactionSummaryRequests[0].toolOutputStructuredError !== true",
);
expect(flow).not.toContain("compactionSummaryRequests.every(");
expect(flow).not.toContain("compactionSummaryRequests.length >= 1");
expect(flow).toContain(
"writeRequest.rawByteLength < config.overflowThresholdBytes && writeRequest.rawByteLength < overflowRequest.rawByteLength",
@@ -154,6 +293,23 @@ describe("qa compaction scenario catalog", () => {
expect(flow).toContain("durable: String(request.allInputText ?? '')");
expect(flow).toContain("bulky: String(request.allInputText ?? '')");
expect(flow).toContain("inputChars: String(request.allInputText ?? '').length");
expect(flow).toContain(
"resolvedWireTool: request.plannedWireToolName ?? request.plannedToolName ?? null",
);
expect(flow).toContain("callId: request.plannedToolCallId ?? null");
expect(flow).toContain("itemId: request.plannedToolItemId ?? null");
expect(flow).toContain(
"transcriptId: typeof request.plannedToolItemId === 'string' && request.plannedToolItemId.length > 0",
);
expect(flow).toContain(": request.plannedToolCallId ?? null");
expect(flow).toContain("logicalWrites=${String(writeRequests.length)}");
expect(flow).toContain("wireTool=${String(writeWireToolName)}");
expect(flow).toContain("callId=${String(writeRequest.plannedToolCallId)}");
expect(flow).toContain("itemId=${String(writeRequest.plannedToolItemId)}");
expect(flow).toContain("transcriptId=${String(writeTranscriptToolCallId)}");
expect(flow).toContain(
"wireSuccesses=${String(transcript.successfulToolCallCounts[writeWireToolName] ?? 0)}",
);
expect(flow).not.toContain("clientSessionId");
expect(flow).toContain("tailBlocks:");
expect(flow).toContain(".sort().slice(0, 16)");
@@ -32,6 +32,20 @@ export function requireFlowScenario(scenario: CatalogScenario): FlowCatalogScena
return scenario;
}
export function readFlowAssertExpression(action: unknown): string {
if (!action || typeof action !== "object" || !("assert" in action)) {
return "";
}
const assertion = action.assert;
if (typeof assertion === "string") {
return assertion;
}
if (!assertion || typeof assertion !== "object" || !("expr" in assertion)) {
return "";
}
return typeof assertion.expr === "string" ? assertion.expr : "";
}
export function flowContainsCall(value: unknown, callName: string): boolean {
if (Array.isArray(value)) {
return value.some((entry) => flowContainsCall(entry, callName));
@@ -592,72 +592,6 @@ describe("qa scenario catalog", () => {
});
});
it("loads live gateway sentinel scenarios for harness self-health", () => {
const scenarioIds = [
"plugin-hook-health-sentinel",
"plugin-manifest-contract-health",
"webchat-direct-reply-routing",
"long-context-progress-watchdog",
"gateway-restart-inflight-run",
"gateway-restart-multi-live",
"streaming-final-integrity",
];
for (const scenarioId of scenarioIds) {
const scenario = readQaScenarioById(scenarioId);
expect(scenario.execution.flow?.steps.length).toBeGreaterThan(0);
expect(scenario.coverage?.primary.length).toBeGreaterThan(0);
}
expect(readQaScenarioById("webchat-direct-reply-routing").sourcePath).toBe(
"qa/scenarios/channels/webchat-direct-reply-routing.yaml",
);
expect(readQaScenarioById("long-context-progress-watchdog").sourcePath).toBe(
"qa/scenarios/runtime/long-context-progress-watchdog.yaml",
);
const gatewayRestartFlow = readQaScenarioById("gateway-restart-inflight-run").execution.flow;
const gatewayRestartContract = JSON.stringify(gatewayRestartFlow);
expect(
JSON.stringify(readQaScenarioById("gateway-restart-inflight-run").gatewayConfigPatch),
).toContain('"alsoAllow":["qa_restart_wait","qa_restart_unsafe_probe"]');
expect(gatewayRestartContract).toContain("plannedToolName === 'wait'");
expect(gatewayRestartContract).toContain("lastAssistantToolNames?.includes('wait')");
expect(gatewayRestartContract).toContain("restartRecoveryDeliveryContext");
expect(gatewayRestartContract).toContain("sendInbound");
expect(gatewayRestartContract).not.toContain("startAgentRun");
expect(gatewayRestartContract).toContain('"restartGatewayWithConfigPatch"');
expect(gatewayRestartContract).toContain("interruptedMatches.length === 1");
expect(gatewayRestartContract).toContain("restartNotices.length === 0");
expect(gatewayRestartContract).toContain("dispatching restart-safe recovery");
expect(gatewayRestartContract).toContain("[OpenClaw heartbeat poll]");
expect(gatewayRestartContract).toContain("liveTurnTimeoutMs(env, 180000)");
expect(gatewayRestartContract).toContain("id: `dm:${conversationId}`");
expect(gatewayRestartContract).toContain("dmScope: env.cfg.session?.dmScope");
expect(readQaScenarioById("gateway-restart-inflight-run").gatewayConfigPatch).toMatchObject({
plugins: {
slots: { memory: "none" },
entries: {
acpx: { enabled: false },
"memory-core": { enabled: false },
},
},
});
const liveMultiRestart = readQaScenarioById("gateway-restart-multi-live");
const liveMultiRestartContract = JSON.stringify(liveMultiRestart.execution.flow);
expect(JSON.stringify(liveMultiRestart.gatewayConfigPatch)).toContain(
'"alsoAllow":["qa_restart_wait","qa_restart_unsafe_probe"]',
);
expect(liveMultiRestartContract).toContain("assistantToolCallCounts.exec");
expect(liveMultiRestartContract).toContain("checkpoint");
expect(liveMultiRestartContract).toContain("restarts=3");
expect(liveMultiRestartContract).toContain("dmScope: 'per-channel-peer'");
expect(liveMultiRestartContract).toContain("dispatching restart-safe recovery");
expect(readQaScenarioExecutionConfig("gateway-restart-multi-live")).toMatchObject({
requiredProviderMode: "live-frontier",
requiredProvider: "openai",
requiredModel: "gpt-5.4",
});
});
it("loads the QA bus tool trace visibility harness scenario", () => {
const scenario = readQaScenarioById("qa-bus-tool-trace-visibility");
const config = readQaScenarioExecutionConfig(scenario.id) as
@@ -89,6 +89,9 @@ flow:
- set: artifactPath
value:
expr: "path.join(env.gateway.workspaceDir, 'repo-contract-summary.txt')"
- set: requestCursorBefore
value:
expr: "env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/request-cursor`)).cursor : 0"
- call: runAgentPrompt
args:
- ref: env
@@ -128,7 +131,7 @@ flow:
expr: "`repo contract followthrough bounced for permission or stalled: ${outbound.text}`"
- set: followthroughDebugRequests
value:
expr: "env.mock ? [...(await fetchJson(`${env.mock.baseUrl}/debug/requests`))].filter((request) => /repo contract followthrough check/i.test(String(request.allInputText ?? ''))) : []"
expr: "env.mock ? [...(await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`))].filter((request) => /repo contract followthrough check/i.test(String(request.allInputText ?? ''))) : []"
- assert:
expr: "!env.mock || followthroughDebugRequests.filter((request) => request.plannedToolName === 'read').length >= 3"
message:
+4 -1
View File
@@ -32,6 +32,9 @@ flow:
- call: reset
- try:
actions:
- set: requestCursorBefore
value:
expr: "env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/request-cursor`)).cursor : 0"
- call: runAgentPrompt
args:
- ref: env
@@ -76,7 +79,7 @@ flow:
# request after the tool runs has plannedToolName unset.
- set: subagentDebugRequests
value:
expr: "env.mock ? [...(await fetchJson(`${env.mock.baseUrl}/debug/requests`))] : []"
expr: "env.mock ? [...(await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`))] : []"
- assert:
expr: "!env.mock || subagentDebugRequests.some((request) => !request.toolOutput && /delegate one bounded qa task/i.test(String(request.allInputText ?? '')) && request.plannedToolName === 'sessions_spawn')"
message:
@@ -19,6 +19,7 @@ scenario:
timeoutMs: 180000
retryCount: 0
config:
requiredChannelDriver: live
matrixConfigOverrides:
streaming: quiet
matrixTopology:
@@ -35,7 +36,17 @@ scenario:
requireMention: true
flow:
module: ./live-transports/matrix/scenarios/scenario-runtime-media.js
call: runGeneratedImageDeliveryScenario
args:
- expr: "scenarioContext"
steps:
- name: Matrix generated images deliver as real image attachments while streaming
actions:
- call: ensureImageGenerationConfigured
args:
- ref: env
- set: scenarioModule
value:
expr: "await qaImport('./live-transports/matrix/scenarios/scenario-runtime-media.js')"
- call: scenarioModule.runGeneratedImageDeliveryScenario
args:
- expr: "scenarioContext"
saveAs: result
detailsExpr: "result.details ?? (result.artifacts ? JSON.stringify(result.artifacts, null, 2) : undefined)"
@@ -90,19 +90,14 @@ flow:
senderName: Alice
text:
expr: config.prompt
- call: waitForOutboundMessage
saveAs: outbound
- call: waitForCondition
saveAs: scenarioRequests
args:
- ref: state
- lambda:
params: [candidate]
expr: "candidate.direction === 'outbound' && candidate.conversation.id === config.channelId && candidate.conversation.kind === 'channel' && candidate.text.includes(config.expectedNeedle)"
async: true
expr: "(async () => { const requests = (await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBeforeInbound}`)).filter((request) => String(request.allInputText ?? '').includes(config.promptSnippet)); return requests.length >= 3 ? requests : undefined; })()"
- expr: liveTurnTimeoutMs(env, 30000)
- sinceIndex:
ref: outboundStartIndex
- set: scenarioRequests
value:
expr: "(await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBeforeInbound}`)).filter((request) => String(request.allInputText ?? '').includes(config.promptSnippet))"
- 100
- assert:
expr: scenarioRequests.length === 3
message:
@@ -128,6 +123,16 @@ flow:
expr: "finalRequest.cursor > searchResultRequest.cursor && finalRequest.toolOutputCallId === searchResultRequest.plannedToolCallId && finalRequest.toolOutputStructuredError !== true && String(finalRequest.toolOutput ?? '').includes(config.expectedMemoryPath) && String(finalRequest.toolOutput ?? '').includes(config.expectedNeedle) && !finalRequest.plannedToolName"
message:
expr: "`final request did not consume the matching successful memory_get result: ${JSON.stringify(finalRequest)}`"
- call: waitForOutboundMessage
saveAs: outbound
args:
- ref: state
- lambda:
params: [candidate]
expr: "candidate.direction === 'outbound' && candidate.conversation.id === config.channelId && candidate.conversation.kind === 'channel' && candidate.text.includes(config.expectedNeedle)"
- expr: liveTurnTimeoutMs(env, 30000)
- sinceIndex:
ref: outboundStartIndex
- call: sleep
args: [4000]
- set: visibleChannelOutbounds
@@ -26,9 +26,12 @@ scenario:
kind: flow
summary: Verify a memory-backed answer requested inside a thread stays in-thread and does not leak into the root channel.
channel: qa-channel
providerMode: mock-openai
retryCount: 0
config:
memoryFact: "Thread-hidden codename: ORBIT-22."
memoryQuery: "hidden thread codename ORBIT-22"
expectedMemoryPath: MEMORY.md
expectedNeedle: "ORBIT-22"
channelId: qa-room
channelTitle: QA Room
@@ -79,7 +82,10 @@ flow:
- assert:
expr: Boolean(threadId)
message: missing thread id for memory isolation check
- set: beforeCursor
- set: requestCursorBefore
value:
expr: "(await fetchJson(`${env.mock.baseUrl}/debug/request-cursor`)).cursor"
- set: outboundStartIndex
value:
expr: state.getSnapshot().messages.length
- sendInbound:
@@ -97,6 +103,39 @@ flow:
ref: threadId
threadTitle:
expr: config.threadTitle
- call: waitForCondition
saveAs: scenarioRequests
args:
- lambda:
async: true
expr: "(async () => { const requests = (await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)).filter((request) => String(request.allInputText ?? '').includes(config.promptSnippet)); return requests.length >= 3 ? requests : undefined; })()"
- expr: liveTurnTimeoutMs(env, 30000)
- 100
- assert:
expr: scenarioRequests.length === 3
message:
expr: "`expected one memory_search plan, one memory_get plan, and one final request: ${JSON.stringify(scenarioRequests)}`"
- set: searchPlanRequest
value:
expr: scenarioRequests[0]
- set: searchResultRequest
value:
expr: scenarioRequests[1]
- set: finalRequest
value:
expr: scenarioRequests[2]
- assert:
expr: "searchPlanRequest.plannedToolName === 'memory_search' && searchPlanRequest.plannedToolArgs?.query === config.memoryQuery && typeof searchPlanRequest.plannedToolCallId === 'string' && searchPlanRequest.plannedToolCallId.length > 0 && !searchPlanRequest.toolOutputCallId && !String(searchPlanRequest.allInputText ?? '').includes(config.expectedNeedle)"
message:
expr: "`initial thread request exposed the hidden fact, disabled memory_search, or routed incorrectly: ${JSON.stringify(searchPlanRequest)}`"
- assert:
expr: "searchResultRequest.cursor > searchPlanRequest.cursor && searchResultRequest.toolOutputCallId === searchPlanRequest.plannedToolCallId && searchResultRequest.toolOutputStructuredError !== true && String(searchResultRequest.toolOutput ?? '').includes(config.expectedMemoryPath) && String(searchResultRequest.toolOutput ?? '').includes(config.expectedNeedle) && searchResultRequest.plannedToolName === 'memory_get' && searchResultRequest.plannedToolArgs?.path === config.expectedMemoryPath && typeof searchResultRequest.plannedToolCallId === 'string' && searchResultRequest.plannedToolCallId.length > 0 && searchResultRequest.plannedToolCallId !== searchPlanRequest.plannedToolCallId"
message:
expr: "`memory_search result did not causally plan memory_get: ${JSON.stringify(searchResultRequest)}`"
- assert:
expr: "finalRequest.cursor > searchResultRequest.cursor && finalRequest.toolOutputCallId === searchResultRequest.plannedToolCallId && finalRequest.toolOutputStructuredError !== true && String(finalRequest.toolOutput ?? '').includes(config.expectedNeedle) && !finalRequest.plannedToolName"
message:
expr: "`final thread request did not consume the matching memory_get result: ${JSON.stringify(finalRequest)}`"
- call: waitForOutboundMessage
saveAs: outbound
args:
@@ -105,10 +144,9 @@ flow:
params: [candidate]
expr: "((candidate.conversation.id === config.channelId && candidate.threadId === threadId) || candidate.conversation.id === threadId) && candidate.text.includes(config.expectedNeedle)"
- expr: liveTurnTimeoutMs(env, 300000)
- sinceIndex:
ref: outboundStartIndex
- assert:
expr: "!state.getSnapshot().messages.slice(beforeCursor).some((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === config.channelId && !candidate.threadId)"
expr: "!state.getSnapshot().messages.slice(outboundStartIndex).some((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === config.channelId && !candidate.threadId)"
message: threaded memory answer leaked into root channel
- assert:
expr: "!env.mock || (await fetchJson(`${env.mock.baseUrl}/debug/requests`)).filter((request) => String(request.allInputText ?? '').includes(config.promptSnippet)).some((request) => request.plannedToolName === 'memory_search')"
message: expected memory_search in thread memory flow
detailsExpr: outbound.text
@@ -89,16 +89,6 @@ flow:
expr: config.prompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 30000)
- call: waitForOutboundMessage
saveAs: outbound
args:
- ref: state
- lambda:
params: [candidate]
expr: "candidate.conversation.id === 'qa-operator' && candidate.text.includes(nonce)"
- expr: liveTurnTimeoutMs(env, 30000)
- sinceIndex:
ref: outboundStartIndex
- set: scenarioRequests
value:
expr: "(await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)).filter((request) => String(request.allInputText ?? '').includes(config.promptSnippet))"
@@ -127,6 +117,16 @@ flow:
expr: "getResultRequest.toolOutputCallId === searchResultRequest.plannedToolCallId && getResultRequest.toolOutputStructuredError !== true && String(getResultRequest.toolOutput ?? '').includes(nonce) && String(getResultRequest.allInputText ?? '').includes(nonce) && !getResultRequest.plannedToolName"
message:
expr: "`memory_get result was not consumed before terminal generation: ${JSON.stringify(getResultRequest)}`"
- call: waitForOutboundMessage
saveAs: outbound
args:
- ref: state
- lambda:
params: [candidate]
expr: "candidate.conversation.id === 'qa-operator' && candidate.text.includes(nonce)"
- expr: liveTurnTimeoutMs(env, 30000)
- sinceIndex:
ref: outboundStartIndex
- set: terminalOutbounds
value:
expr: "state.getSnapshot().messages.filter((message) => message.direction === 'outbound').slice(outboundStartIndex)"
@@ -12,7 +12,7 @@ scenario:
objective: Verify OpenClaw persists one overflow compaction, prunes bulky historical turns, retries from durable context, and performs the requested mutation exactly once.
successCriteria:
- One coded over-threshold provider overflow produces one persisted OpenClaw overflow compaction and one compacted retry retaining durable current context.
- OpenClaw performs exactly one successful write, one causal continuation, and returns the exact file content and final marker.
- OpenClaw performs exactly one successful write, then one terminal continuation after zero-or-more causally linked waits, and returns the exact file content and final marker.
- OpenClaw proves session-memory.pruning by retaining a nonempty contiguous suffix ending at block 15 while pruning marker block 10.
- OpenClaw records one overflow-retry checkpoint whose branch preserves the independent pre-compaction assistant marker.
- The Codex runtime-pair cell reports a known harness gap before gateway, session, or provider work and makes no compaction coverage claim.
@@ -25,6 +25,7 @@ scenario:
execution:
kind: flow
summary: Force one OpenClaw context overflow, verify persisted pruning evidence, and prove exactly-once mutation after compaction.
retryCount: 0
config:
outputFile: compaction-retry-summary.txt
promptSnippet: Compaction retry mutating tool check
@@ -33,7 +34,6 @@ scenario:
checkpointMarker: QA-COMPACTION-PRECOMPACTION-ASSISTANT-MARKER
overflowThresholdBytes: 262144
expectedFileContent: "Replay safety: unsafe after write.\n"
expectedWriteToolResult: Successfully wrote 35 bytes to compaction-retry-summary.txt
finalMarker: "Protocol note: replay unsafe after write."
prompt: |-
Compaction retry mutating tool check. Current durable context marker: QA-COMPACTION-DURABLE-MARKER.
@@ -97,25 +97,12 @@ flow:
ref: config.prompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 120000)
transcriptToolName: write
requireSuccessfulTranscriptToolResult: true
- call: waitForCondition
saveAs: outbound
args:
- lambda:
expr: "state.getSnapshot().messages.filter((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === 'qa-operator' && candidate.text.includes(config.finalMarker)).at(-1)"
- expr: liveTurnTimeoutMs(env, 45000)
- 100
- assert:
expr: "outbound.text === config.finalMarker"
message:
expr: "`unexpected final compaction reply: ${JSON.stringify(outbound.text)}`"
- set: scenarioRequests
value:
expr: "await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)"
- set: requestEvidence
value:
expr: "scenarioRequests.map((request) => ({ cursor: request.cursor, kind: request.requestKind, outcome: request.outcome, code: request.errorCode ?? null, bytes: request.rawByteLength, inputChars: String(request.allInputText ?? '').length, tailBlocks: [...new Set(Array.from({ length: 16 }, (_, index) => String(index).padStart(2, '0')).filter((id) => String(request.allInputText ?? '').includes(`post-marker historical user block ${id}`)))].sort().slice(0, 16), prompt: String(request.allInputText ?? '').includes(config.promptSnippet), durable: String(request.allInputText ?? '').includes(config.durableMarker), bulky: String(request.allInputText ?? '').includes(config.bulkyMarker), tool: request.plannedToolName ?? null }))"
expr: "scenarioRequests.map((request) => ({ cursor: request.cursor, kind: request.requestKind, outcome: request.outcome, code: request.errorCode ?? null, bytes: request.rawByteLength, inputChars: String(request.allInputText ?? '').length, tailBlocks: [...new Set(Array.from({ length: 16 }, (_, index) => String(index).padStart(2, '0')).filter((id) => String(request.allInputText ?? '').includes(`post-marker historical user block ${id}`)))].sort().slice(0, 16), prompt: String(request.allInputText ?? '').includes(config.promptSnippet), durable: String(request.allInputText ?? '').includes(config.durableMarker), bulky: String(request.allInputText ?? '').includes(config.bulkyMarker), tool: request.plannedToolName ?? null, resolvedWireTool: request.plannedWireToolName ?? request.plannedToolName ?? null, callId: request.plannedToolCallId ?? null, itemId: request.plannedToolItemId ?? null, transcriptId: typeof request.plannedToolItemId === 'string' && request.plannedToolItemId.length > 0 ? `${request.plannedToolCallId}|${request.plannedToolItemId}` : request.plannedToolCallId ?? null }))"
- set: overflowRequests
value:
expr: "scenarioRequests.filter((request) => request.requestKind === 'agent-initial' && request.outcome === 'error' && request.errorCode === 'context_length_exceeded' && String(request.allInputText ?? '').includes(sessionId) && String(request.allInputText ?? '').includes(config.promptSnippet) && String(request.allInputText ?? '').includes(config.durableMarker))"
@@ -128,7 +115,7 @@ flow:
expr: "overflowRequests[0]"
- set: writeRequests
value:
expr: "scenarioRequests.filter((request) => request.requestKind === 'agent-initial' && request.outcome === 'success' && request.plannedToolName === 'write' && !request.toolOutput && request.cursor > overflowRequest.cursor && String(request.allInputText ?? '').includes(sessionId) && String(request.allInputText ?? '').includes(config.promptSnippet) && String(request.allInputText ?? '').includes(config.durableMarker) && request.plannedToolArgs?.path === config.outputFile && request.plannedToolArgs?.content === config.expectedFileContent)"
expr: "scenarioRequests.filter((request) => request.cursor > overflowRequest.cursor && request.plannedToolName === 'write' && String(request.allInputText ?? '').includes(sessionId) && String(request.allInputText ?? '').includes(config.promptSnippet) && String(request.allInputText ?? '').includes(config.durableMarker))"
- assert:
expr: "writeRequests.length === 1"
message:
@@ -136,6 +123,16 @@ flow:
- set: writeRequest
value:
expr: "writeRequests[0]"
- assert:
expr: "writeRequest.requestKind === 'agent-initial' && writeRequest.outcome === 'success' && !writeRequest.toolOutput && writeRequest.plannedToolArgs?.path === config.outputFile && writeRequest.plannedToolArgs?.content === config.expectedFileContent && (writeRequest.plannedWireToolName === undefined || writeRequest.plannedWireToolName === 'exec') && typeof writeRequest.plannedToolCallId === 'string' && writeRequest.plannedToolCallId.length > 0"
message:
expr: "`logical write did not have the expected successful direct-or-Code-Mode wire shape: ${JSON.stringify(requestEvidence.find((request) => request.cursor === writeRequest.cursor))}`"
- set: writeWireToolName
value:
expr: "writeRequest.plannedWireToolName ?? writeRequest.plannedToolName"
- set: writeTranscriptToolCallId
value:
expr: "typeof writeRequest.plannedToolItemId === 'string' && writeRequest.plannedToolItemId.length > 0 ? `${writeRequest.plannedToolCallId}|${writeRequest.plannedToolItemId}` : writeRequest.plannedToolCallId"
- set: overflowEvidence
value:
expr: "requestEvidence.find((request) => request.cursor === overflowRequest.cursor)"
@@ -147,10 +144,17 @@ flow:
args:
- ref: env
- ref: sessionKey
- set: successfulWriteTranscriptEvents
value:
expr: "(transcript.successfulToolCallEvents ?? []).filter((event) => event.toolCallId === writeTranscriptToolCallId && event.name === writeWireToolName)"
- assert:
expr: "transcript.successfulToolCallCounts.write === 1"
expr: "successfulWriteTranscriptEvents.length === 1"
message:
expr: "`expected exactly one successful write, got ${JSON.stringify(transcript.successfulToolCallCounts)}`"
expr: "`expected one authenticated successful transcript event for the logical write's wire call: ${JSON.stringify({ write: writeEvidence, wireTool: writeWireToolName, callId: writeRequest.plannedToolCallId, itemId: writeRequest.plannedToolItemId, transcriptId: writeTranscriptToolCallId, events: transcript.successfulToolCallEvents ?? [] })}`"
- assert:
expr: "transcript.successfulToolCallCounts[writeWireToolName] === 1"
message:
expr: "`expected exactly one successful resolved wire call, got ${JSON.stringify({ wireTool: writeWireToolName, counts: transcript.successfulToolCallCounts })}`"
- assert:
expr: "overflowRequest.rawByteLength > config.overflowThresholdBytes"
message:
@@ -161,11 +165,44 @@ flow:
expr: "`compacted retry did not retain durable current context: ${JSON.stringify({ overflow: overflowEvidence, write: writeEvidence })}`"
- set: postWriteContinuations
value:
expr: "scenarioRequests.filter((request) => request.requestKind === 'tool-continuation' && request.outcome === 'success' && request.plannedToolName === undefined && request.cursor > writeRequest.cursor && request.toolOutputCallId === writeRequest.plannedToolCallId && String(request.allInputText ?? '').includes(sessionId) && String(request.toolOutput ?? '').trim() === config.expectedWriteToolResult)"
expr: "scenarioRequests.filter((request) => request.requestKind === 'tool-continuation' && request.cursor > writeRequest.cursor && String(request.allInputText ?? '').includes(sessionId)).toSorted((left, right) => left.cursor - right.cursor)"
- set: continuationChain
value:
expr: "(() => { const requests = []; const waits = []; const followedCallIds = new Set(); let currentCallId = writeRequest.plannedToolCallId; let previousCursor = writeRequest.cursor; let terminal; let valid = typeof currentCallId === 'string' && currentCallId.length > 0; while (valid) { if (followedCallIds.has(currentCallId)) { valid = false; break; } followedCallIds.add(currentCallId); const matches = postWriteContinuations.filter((request) => request.cursor > previousCursor && request.toolOutputCallId === currentCallId); if (matches.length !== 1) { valid = false; break; } const request = matches[0]; requests.push(request); previousCursor = request.cursor; if (request.plannedToolName === 'wait') { if (typeof request.plannedToolCallId !== 'string' || request.plannedToolCallId.length === 0) { valid = false; break; } waits.push(request); currentCallId = request.plannedToolCallId; continue; } if (request.plannedToolName === undefined) { terminal = request; break; } valid = false; } return { valid, requests, waits, terminal }; })()"
- assert:
expr: "postWriteContinuations.length === 1"
expr: "continuationChain.valid === true && continuationChain.requests.length === postWriteContinuations.length && continuationChain.requests.every((request, index) => request === postWriteContinuations[index]) && continuationChain.requests.length === continuationChain.waits.length + 1 && continuationChain.requests.every((request) => request.outcome === 'success' && request.toolOutputStructuredError !== true)"
message:
expr: "`expected one post-write continuation and no replayed mutation: ${JSON.stringify(requestEvidence.filter((request) => request.cursor > writeRequest.cursor))}`"
expr: "`expected one exhaustive successful causal continuation chain after the logical write: ${JSON.stringify(postWriteContinuations)}`"
- set: terminalContinuations
value:
expr: "continuationChain.requests.filter((request) => request.plannedToolName === undefined)"
- assert:
expr: "terminalContinuations.length === 1 && terminalContinuations[0] === continuationChain.terminal && String(terminalContinuations[0].toolOutput ?? '').trim().length > 0 && terminalContinuations[0].toolOutputStructuredError !== true"
message:
expr: "`expected exactly one non-error terminal continuation after zero-or-more linked waits: ${JSON.stringify(postWriteContinuations)}`"
- assert:
expr: "continuationChain.waits.every((request) => request.plannedToolName === 'wait' && typeof request.plannedToolCallId === 'string' && request.plannedToolCallId.length > 0) && new Set([writeRequest.plannedToolCallId, ...continuationChain.waits.map((request) => request.plannedToolCallId)]).size === continuationChain.waits.length + 1"
message:
expr: "`linked wait calls did not preserve distinct causal call ids: ${JSON.stringify(postWriteContinuations)}`"
- assert:
expr: "continuationChain.waits.length === 0 || (continuationChain.waits.every((request) => typeof request.plannedToolArgs?.cell_id === 'string' && request.plannedToolArgs.cell_id.length > 0) && new Set(continuationChain.waits.map((request) => request.plannedToolArgs.cell_id)).size === 1)"
message:
expr: "`linked waits did not preserve one nonempty Code Mode cell id: ${JSON.stringify(postWriteContinuations)}`"
- assert:
expr: "writeWireToolName !== 'exec' || (terminalContinuations[0].providerVariant === 'openai' ? String(terminalContinuations[0].toolOutput ?? '').startsWith('Script completed\\n') : terminalContinuations[0].providerVariant === 'anthropic' ? (() => { try { const parsed = JSON.parse(String(terminalContinuations[0].toolOutput ?? '')); return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) && parsed.status === 'completed'; } catch { return false; } })() : false)"
message:
expr: "`Code Mode terminal continuation did not report successful completion: ${JSON.stringify(terminalContinuations[0])}`"
- call: waitForCondition
saveAs: outbound
args:
- lambda:
expr: "state.getSnapshot().messages.filter((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === 'qa-operator' && candidate.text.includes(config.finalMarker)).at(-1)"
- expr: liveTurnTimeoutMs(env, 45000)
- 100
- assert:
expr: "outbound.text === config.finalMarker"
message:
expr: "`unexpected final compaction reply: ${JSON.stringify(outbound.text)}`"
- call: fs.readFile
saveAs: writtenSummary
args:
@@ -191,9 +228,9 @@ flow:
value:
expr: "scenarioRequests.filter((request) => request.requestKind === 'compaction-summary')"
- assert:
expr: "compactionSummaryRequests.every((request) => request.outcome === 'success' && request.plannedToolName === undefined)"
expr: "compactionSummaryRequests.length === 1 && compactionSummaryRequests[0].outcome === 'success' && compactionSummaryRequests[0].plannedToolName === undefined && compactionSummaryRequests[0].toolOutputStructuredError !== true"
message:
expr: "`OpenClaw compaction summary requests included a failure or tool plan: ${JSON.stringify(requestEvidence.filter((request) => request.kind === 'compaction-summary'))}`"
expr: "`expected exactly one successful OpenClaw compaction summary request: ${JSON.stringify(requestEvidence.filter((request) => request.kind === 'compaction-summary'))}`"
- call: readRawQaSessionStore
saveAs: store
args:
@@ -227,4 +264,4 @@ flow:
expr: "branchSummary.finalText === config.checkpointMarker"
message:
expr: "`checkpoint branch did not preserve pre-compaction assistant marker: ${JSON.stringify({ key: branchResult.key, finalText: branchSummary.finalText })}`"
detailsExpr: "`${outbound.text}\\nOpenClaw originalBytes=${String(overflowRequest.rawByteLength)} retryBytes=${String(writeRequest.rawByteLength)} writes=${String(transcript.successfulToolCallCounts.write ?? 0)} compactions=${String(sessionEntry.compactionCount)} checkpoints=${String(overflowCheckpoints.length)}`"
detailsExpr: "`${outbound.text}\\nOpenClaw originalBytes=${String(overflowRequest.rawByteLength)} retryBytes=${String(writeRequest.rawByteLength)} logicalWrites=${String(writeRequests.length)} wireTool=${String(writeWireToolName)} callId=${String(writeRequest.plannedToolCallId)} itemId=${String(writeRequest.plannedToolItemId)} transcriptId=${String(writeTranscriptToolCallId)} wireSuccesses=${String(transcript.successfulToolCallCounts[writeWireToolName] ?? 0)} compactions=${String(sessionEntry.compactionCount)} checkpoints=${String(overflowCheckpoints.length)}`"
@@ -44,6 +44,7 @@ scenario:
kind: flow
runtime: openclaw
timeoutMs: 420000
retryCount: 0
summary: Restart while replay-safe Code Mode wait is executing, then verify automatic reconstruction and delivery.
config:
requiredProviderMode: mock-openai
@@ -136,18 +137,34 @@ flow:
args:
- ref: env
- 180000
- call: sleep
- call: waitForCondition
saveAs: settledRecovery
args:
- 6500
- call: readSessionTranscriptSummary
saveAs: postRestartTranscript
args:
- ref: env
- ref: sessionKey
- lambda:
async: true
expr: "(async () => { const recoveryRequests = env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)) : []; const restartRecoveryRequests = recoveryRequests.filter((request) => String(request.prompt ?? '').includes('Your previous turn was interrupted by a gateway restart') && String(request.allInputText ?? '').includes(config.interruptedMarker)); const transcript = await readSessionTranscriptSummary(env, sessionKey); return restartRecoveryRequests.length >= 1 && transcript.lastMessageRole === 'assistant' && String(transcript.finalText ?? '').includes(config.interruptedMarker) ? { recoveryRequests, restartRecoveryRequests, transcript } : undefined; })()"
- expr: liveTurnTimeoutMs(env, 120000)
- 25
- set: recoveryRequestsBeforeOutbound
value:
expr: settledRecovery.recoveryRequests
- set: restartRecoveryRequestsBeforeOutbound
value:
expr: settledRecovery.restartRecoveryRequests
- set: postRestartTranscript
value:
expr: settledRecovery.transcript
- assert:
expr: "postRestartTranscript.lastAssistantStopReason !== 'error' && postRestartTranscript.lastAssistantStopReason !== 'aborted'"
message:
expr: "`restart recovery left an abort artifact: ${JSON.stringify(postRestartTranscript)}`"
- assert:
expr: "restartRecoveryRequestsBeforeOutbound.length === 1 && String(restartRecoveryRequestsBeforeOutbound[0].prompt ?? '').includes('Your previous turn was interrupted by a gateway restart') && String(restartRecoveryRequestsBeforeOutbound[0].allInputText ?? '').includes(config.interruptedMarker)"
message:
expr: "`expected exactly one scoped restart recovery provider request before delivery, got ${restartRecoveryRequestsBeforeOutbound.length}; total post-cursor requests=${recoveryRequestsBeforeOutbound.length}`"
- assert:
expr: "!String(restartRecoveryRequestsBeforeOutbound[0].prompt ?? '').includes('[OpenClaw heartbeat poll]')"
message: restart recovery provider request was replaced by a heartbeat poll
- call: waitForOutboundMessage
saveAs: outbound
args:
@@ -158,6 +175,16 @@ flow:
- expr: liveTurnTimeoutMs(env, 180000)
- sinceIndex:
ref: startIndex
- set: settledRecoveryRequests
value:
expr: "env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)) : []"
- set: settledRestartRecoveryRequests
value:
expr: "settledRecoveryRequests.filter((request) => String(request.prompt ?? '').includes('Your previous turn was interrupted by a gateway restart') && String(request.allInputText ?? '').includes(config.interruptedMarker))"
- assert:
expr: "settledRestartRecoveryRequests.length === 1 && settledRestartRecoveryRequests[0].cursor === restartRecoveryRequestsBeforeOutbound[0].cursor"
message:
expr: "`restart recovery provider request was duplicated after delivery: before=${JSON.stringify(restartRecoveryRequestsBeforeOutbound)} settled=${JSON.stringify(settledRestartRecoveryRequests)}`"
- set: interruptedMatches
value:
expr: "state.getSnapshot().messages.slice(startIndex).filter((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === conversationId && candidate.text.includes(config.interruptedMarker))"
@@ -172,15 +199,6 @@ flow:
expr: "restartNotices.length === 0"
message:
expr: "`automatic recovery emitted ${restartNotices.length} resend notice(s); outbound=${recentOutboundSummary(state)}`"
- set: recoveryRequests
value:
expr: "env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/requests?after=${requestCursorBefore}`)) : []"
- assert:
expr: "recoveryRequests.some((request) => String(request.allInputText ?? '').includes('Your previous turn was interrupted by a gateway restart'))"
message: restart recovery prompt did not reach the provider
- assert:
expr: "recoveryRequests.every((request) => !String(request.allInputText ?? '').includes('[OpenClaw heartbeat poll]'))"
message: heartbeat poll raced the restart recovery proof
- set: recoveryLogs
value:
expr: readGatewayLogs().slice(gatewayLogCursor)
@@ -188,4 +206,4 @@ flow:
expr: "recoveryLogs.includes('dispatching restart-safe recovery') && recoveryLogs.includes('restart-safe recovery tool policy retained')"
message:
expr: "`restart-safe host policy was not observed; logs=${recoveryLogs}`"
detailsExpr: "`session=${sessionKey} plannedTool=${plannedWait.plannedToolName} persistedTail=${interruptedTranscript.lastMessageRole}/${interruptedTranscript.lastAssistantStopReason}/${interruptedTranscript.lastAssistantToolNames?.join(',')} recoveredTail=${postRestartTranscript.lastMessageRole}/${postRestartTranscript.lastAssistantStopReason}/${postRestartTranscript.lastAssistantContentTypes?.join(',')} recoveredMarkers=${interruptedMatches.length} resendNotices=${restartNotices.length} restartSafePolicy=true heartbeatPoll=false\\n${outbound.text}`"
detailsExpr: "`session=${sessionKey} plannedTool=${plannedWait.plannedToolName} persistedTail=${interruptedTranscript.lastMessageRole}/${interruptedTranscript.lastAssistantStopReason}/${interruptedTranscript.lastAssistantToolNames?.join(',')} recoveredTail=${postRestartTranscript.lastMessageRole}/${postRestartTranscript.lastAssistantStopReason}/${postRestartTranscript.lastAssistantContentTypes?.join(',')} recoveredMarkers=${interruptedMatches.length} resendNotices=${restartNotices.length} restartSafePolicy=true recoveryPromptHeartbeat=false\\n${outbound.text}`"