test: clear embedded attempt broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 10:37:17 +01:00
parent c8f7cea0d6
commit 4a81aaa0c5

View File

@@ -73,21 +73,51 @@ async function invokeWrappedTestStream(
return await Promise.resolve(wrappedFn({} as never, {} as never, {} as never)); return await Promise.resolve(wrappedFn({} as never, {} as never, {} as never));
} }
function requireRecord(value: unknown, label: string): Record<string, unknown> {
expect(value, label).toBeTypeOf("object");
expect(value, label).not.toBeNull();
return value as Record<string, unknown>;
}
function requireContentItem(
content: Array<{ type?: string; text?: string; name?: string }> | unknown[],
index = 0,
) {
return requireRecord(content[index], `content item ${index}`);
}
function expectSingleTextContent(
content: Array<{ type?: string; text?: string }> | unknown[],
textFragment: string,
) {
expect(content).toHaveLength(1);
const item = requireContentItem(content);
expect(item.type).toBe("text");
expect(item.text).toContain(textFragment);
}
function expectSingleToolCallContent(
content: Array<{ type?: string; name?: string }> | unknown[],
name: string,
) {
expect(content).toHaveLength(1);
const item = requireContentItem(content);
expect(item.type).toBe("toolCall");
expect(item.name).toBe(name);
}
describe("buildEmbeddedAttemptToolRunContext", () => { describe("buildEmbeddedAttemptToolRunContext", () => {
it("carries runtime toolsAllow into coding tool construction", () => { it("carries runtime toolsAllow into coding tool construction", () => {
expect( const context = buildEmbeddedAttemptToolRunContext({
buildEmbeddedAttemptToolRunContext({
trigger: "manual",
jobId: "job-1",
memoryFlushWritePath: "memory/log.md",
toolsAllow: ["memory_search", "memory_get"],
}),
).toMatchObject({
trigger: "manual", trigger: "manual",
jobId: "job-1", jobId: "job-1",
memoryFlushWritePath: "memory/log.md", memoryFlushWritePath: "memory/log.md",
runtimeToolAllowlist: ["memory_search", "memory_get"], toolsAllow: ["memory_search", "memory_get"],
}); });
expect(context.trigger).toBe("manual");
expect(context.jobId).toBe("job-1");
expect(context.memoryFlushWritePath).toBe("memory/log.md");
expect(context.runtimeToolAllowlist).toEqual(["memory_search", "memory_get"]);
}); });
}); });
@@ -280,15 +310,9 @@ describe("normalizeMessagesForLlmBoundary", () => {
) as unknown as Array<Record<string, unknown>>; ) as unknown as Array<Record<string, unknown>>;
expect(output).toHaveLength(3); expect(output).toHaveLength(3);
expect(output).not.toEqual( expect(output.some((item) => item.content === "old secret runtime context")).toBe(false);
expect.arrayContaining([expect.objectContaining({ content: "old secret runtime context" })]), expect(output.some((item) => item.content === "secret runtime context")).toBe(true);
); expect(output.some((item) => item.customType === "other-extension-context")).toBe(true);
expect(output).toEqual(
expect.arrayContaining([expect.objectContaining({ content: "secret runtime context" })]),
);
expect(output).toEqual(
expect.arrayContaining([expect.objectContaining({ customType: "other-extension-context" })]),
);
}); });
it("keeps only safe blocked metadata at the LLM boundary", () => { it("keeps only safe blocked metadata at the LLM boundary", () => {
@@ -720,10 +744,8 @@ describe("mergeOrphanedTrailingUserPrompt", () => {
} as never, } as never,
}); });
expect(result).toMatchObject({ expect(result.merged).toBe(true);
merged: true, expect(result.removeLeaf).toBe(true);
removeLeaf: true,
});
expect(result.prompt).toContain("please inspect this inline image"); expect(result.prompt).toContain("please inspect this inline image");
expect(result.prompt).toContain("[image_url] inline data URI (image/png, 4118 chars)"); expect(result.prompt).toContain("[image_url] inline data URI (image/png, 4118 chars)");
expect(result.prompt).not.toContain("base64"); expect(result.prompt).not.toContain("base64");
@@ -748,10 +770,8 @@ describe("mergeOrphanedTrailingUserPrompt", () => {
} as never, } as never,
}); });
expect(result).toMatchObject({ expect(result.merged).toBe(true);
merged: true, expect(result.removeLeaf).toBe(true);
removeLeaf: true,
});
expect(result.prompt).toContain("[value] inline data URI (image/png, 10022 chars)"); expect(result.prompt).toContain("[value] inline data URI (image/png, 10022 chars)");
expect(result.prompt).toContain("bbbb"); expect(result.prompt).toContain("bbbb");
expect(result.prompt).toContain("(2000 chars)"); expect(result.prompt).toContain("(2000 chars)");
@@ -826,11 +846,12 @@ describe("resolveEmbeddedAgentStreamFn", () => {
}, },
}); });
await expect( const streamOptions = await streamFn(
streamFn({ provider: "demo-provider", id: "demo-model" } as never, {} as never, {}), { provider: "demo-provider", id: "demo-model" } as never,
).resolves.toMatchObject({ {} as never,
apiKey: "demo-runtime-key", {},
}); );
expect(requireRecord(streamOptions, "stream options").apiKey).toBe("demo-runtime-key");
expect(providerStreamFn).toHaveBeenCalledTimes(1); expect(providerStreamFn).toHaveBeenCalledTimes(1);
}); });
@@ -847,17 +868,16 @@ describe("resolveEmbeddedAgentStreamFn", () => {
} as never, } as never,
}); });
await expect( const context = await streamFn(
streamFn( { provider: "demo-provider", id: "demo-model" } as never,
{ provider: "demo-provider", id: "demo-model" } as never, {
{ systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`,
systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`, } as never,
} as never, {},
{}, );
), expect(requireRecord(context, "stream context").systemPrompt).toBe(
).resolves.toMatchObject({ "Stable prefix\nDynamic suffix",
systemPrompt: "Stable prefix\nDynamic suffix", );
});
expect(providerStreamFn).toHaveBeenCalledTimes(1); expect(providerStreamFn).toHaveBeenCalledTimes(1);
}); });
it("routes supported default streamSimple fallbacks through boundary-aware transports", () => { it("routes supported default streamSimple fallbacks through boundary-aware transports", () => {
@@ -1154,10 +1174,9 @@ describe("wrapStreamFnTrimToolCallNames", () => {
for (let i = 0; i < 10; i += 1) { for (let i = 0; i < 10; i += 1) {
const stream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never)); const stream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never));
const result = await stream.result(); const result = await stream.result();
expect(result).toMatchObject({ const message = requireRecord(result, "result message");
role: "assistant", expect(message.role).toBe("assistant");
content: [{ type: "toolCall", name: "exec" }], expectSingleToolCallContent(message.content as unknown[], "exec");
});
} }
const blockedStream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never)); const blockedStream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never));
@@ -1167,12 +1186,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(blockedResult.role).toBe("assistant"); expect(blockedResult.role).toBe("assistant");
expect(blockedResult.content).toEqual([ expectSingleTextContent(blockedResult.content, '"exec"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"exec"'),
}),
]);
}); });
it("leaves repeated unavailable tool calls alone when the unknown-tool guard is disabled", async () => { it("leaves repeated unavailable tool calls alone when the unknown-tool guard is disabled", async () => {
@@ -1190,10 +1204,9 @@ describe("wrapStreamFnTrimToolCallNames", () => {
for (let i = 0; i < 11; i += 1) { for (let i = 0; i < 11; i += 1) {
const stream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never)); const stream = await Promise.resolve(wrappedFn({} as never, {} as never, {} as never));
const result = await stream.result(); const result = await stream.result();
expect(result).toMatchObject({ const message = requireRecord(result, "result message");
role: "assistant", expect(message.role).toBe("assistant");
content: [{ type: "toolCall", name: "exec" }], expectSingleToolCallContent(message.content as unknown[], "exec");
});
} }
}); });
@@ -1221,7 +1234,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
expect(partialToolCall.name).toBe("exec"); expect(partialToolCall.name).toBe("exec");
expect(messageToolCall.name).toBe("exec"); expect(messageToolCall.name).toBe("exec");
expect(result.content).toEqual([expect.objectContaining({ type: "toolCall", name: "exec" })]); expectSingleToolCallContent(result.content, "exec");
}); });
it("does not reset the unavailable-tool streak on partial-only stream chunks", async () => { it("does not reset the unavailable-tool streak on partial-only stream chunks", async () => {
@@ -1256,12 +1269,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(secondResult.role).toBe("assistant"); expect(secondResult.role).toBe("assistant");
expect(secondResult.content).toEqual([ expectSingleTextContent(secondResult.content, '"exec"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"exec"'),
}),
]);
}); });
it("counts the final unknown-tool retry when streamed messages omit the tool name", async () => { it("counts the final unknown-tool retry when streamed messages omit the tool name", async () => {
@@ -1296,12 +1304,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(secondResult.role).toBe("assistant"); expect(secondResult.role).toBe("assistant");
expect(secondResult.content).toEqual([ expectSingleTextContent(secondResult.content, '"exec"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"exec"'),
}),
]);
}); });
it("resets a provisional streamed unknown-tool retry when later chunks resolve to an allowed tool", async () => { it("resets a provisional streamed unknown-tool retry when later chunks resolve to an allowed tool", async () => {
@@ -1351,12 +1354,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(secondResult.role).toBe("assistant"); expect(secondResult.role).toBe("assistant");
expect(secondResult.content).toEqual([ expectSingleToolCallContent(secondResult.content, "ex");
expect.objectContaining({
type: "toolCall",
name: "ex",
}),
]);
}); });
it("keeps processing later streamed messages after one streamed unknown-tool retry was counted", async () => { it("keeps processing later streamed messages after one streamed unknown-tool retry was counted", async () => {
@@ -1406,12 +1404,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(secondResult.role).toBe("assistant"); expect(secondResult.role).toBe("assistant");
expect(secondResult.content).toEqual([ expectSingleToolCallContent(secondResult.content, "re");
expect.objectContaining({
type: "toolCall",
name: "re",
}),
]);
}); });
it("resets a stale unknown-tool streak when a streamed message mixes allowed and unknown tools", async () => { it("resets a stale unknown-tool streak when a streamed message mixes allowed and unknown tools", async () => {
@@ -1475,12 +1468,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
}; };
expect(thirdResult.role).toBe("assistant"); expect(thirdResult.role).toBe("assistant");
expect(thirdResult.content).toEqual([ expectSingleToolCallContent(thirdResult.content, "ex");
expect.objectContaining({
type: "toolCall",
name: "ex",
}),
]);
}); });
it("infers tool names from malformed toolCallId variants when allowlist is present", async () => { it("infers tool names from malformed toolCallId variants when allowlist is present", async () => {
@@ -1626,12 +1614,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
content: Array<{ type: string; text?: string }>; content: Array<{ type: string; text?: string }>;
}; };
expect(result.content).toEqual([ expectSingleTextContent(result.content, '"blank tool name"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"blank tool name"'),
}),
]);
expect(finalToolCall.name).toBe(""); expect(finalToolCall.name).toBe("");
expect(finalToolCall.id).toBe("call_auto_1"); expect(finalToolCall.id).toBe("call_auto_1");
}); });
@@ -1764,12 +1747,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
content: Array<{ type: string; text?: string }>; content: Array<{ type: string; text?: string }>;
}; };
expect(result.content).toEqual([ expectSingleTextContent(result.content, '"blank tool name"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"blank tool name"'),
}),
]);
expect(finalToolCall.name).toBe(""); expect(finalToolCall.name).toBe("");
}); });
it("leaves provisional blank streamed names recoverable while stopping final blank dispatch", async () => { it("leaves provisional blank streamed names recoverable while stopping final blank dispatch", async () => {
@@ -1790,12 +1768,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
content: Array<{ type: string; text?: string }>; content: Array<{ type: string; text?: string }>;
}; };
expect(result.content).toEqual([ expectSingleTextContent(result.content, '"blank tool name"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"blank tool name"'),
}),
]);
expect(partialToolCall.name).toBe(" "); expect(partialToolCall.name).toBe(" ");
expect(finalToolCall.name).toBe("\t "); expect(finalToolCall.name).toBe("\t ");
expect(baseFn).toHaveBeenCalledTimes(1); expect(baseFn).toHaveBeenCalledTimes(1);
@@ -1816,12 +1789,7 @@ describe("wrapStreamFnTrimToolCallNames", () => {
content: Array<{ type: string; text?: string }>; content: Array<{ type: string; text?: string }>;
}; };
expect(result.content).toEqual([ expectSingleTextContent(result.content, '"blank tool name"');
expect.objectContaining({
type: "text",
text: expect.stringContaining('"blank tool name"'),
}),
]);
expect(finalToolCall.name).toBe(""); expect(finalToolCall.name).toBe("");
}); });
@@ -2300,32 +2268,30 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
expect(baseFn).toHaveBeenCalledTimes(1); expect(baseFn).toHaveBeenCalledTimes(1);
const seenContext = baseFn.mock.calls[0]?.[1] as { messages: unknown[] }; const seenContext = baseFn.mock.calls[0]?.[1] as { messages: unknown[] };
expect(seenContext.messages).toEqual([ expect(seenContext.messages).toHaveLength(3);
expect(seenContext.messages[0]).toEqual({
role: "assistant",
content: [
{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
{ type: "toolCall", id: "call_1", name: "read", arguments: {} },
],
});
const repairedToolResult = requireRecord(seenContext.messages[1], "repaired tool result");
expect(repairedToolResult.role).toBe("toolResult");
expect(repairedToolResult.toolCallId).toBe("call_1");
expect(repairedToolResult.toolName).toBe("read");
expect(repairedToolResult.content).toEqual([
{ {
role: "assistant", type: "text",
content: [ text: "[openclaw] missing tool result in session history; inserted synthetic error result for transcript repair.",
{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
{ type: "toolCall", id: "call_1", name: "read", arguments: {} },
],
},
{
role: "toolResult",
toolCallId: "call_1",
toolName: "read",
content: [
{
type: "text",
text: "[openclaw] missing tool result in session history; inserted synthetic error result for transcript repair.",
},
],
isError: true,
timestamp: expect.any(Number),
},
{
role: "user",
content: [{ type: "text", text: "retry" }],
}, },
]); ]);
expect(repairedToolResult.isError).toBe(true);
expect(repairedToolResult.timestamp).toBeTypeOf("number");
expect(seenContext.messages[2]).toEqual({
role: "user",
content: [{ type: "text", text: "retry" }],
});
}); });
it("preserves sessions_spawn attachment payloads on replay", async () => { it("preserves sessions_spawn attachment payloads on replay", async () => {
@@ -3482,15 +3448,16 @@ describe("buildAfterTurnRuntimeContext", () => {
activeAgentId: "main", activeAgentId: "main",
}); });
expect(legacy.activeProcessSessions).toEqual([ const activeProcessSessions = legacy.activeProcessSessions as
expect.objectContaining({ | Array<{ sessionId?: string; command?: string; pid?: number }>
sessionId: "sess-session-id", | undefined;
command: "sleep 600", expect(activeProcessSessions).toHaveLength(1);
pid: 1234, const activeSession = requireRecord(activeProcessSessions?.[0], "active process session");
}), expect(activeSession.sessionId).toBe("sess-session-id");
]); expect(activeSession.command).toBe("sleep 600");
expect(legacy.activeProcessSessions).not.toEqual( expect(activeSession.pid).toBe(1234);
expect.arrayContaining([expect.objectContaining({ sessionId: "sess-other" })]), expect(activeProcessSessions?.some((session) => session.sessionId === "sess-other")).toBe(
false,
); );
} finally { } finally {
resetProcessRegistryForTests(); resetProcessRegistryForTests();
@@ -3519,10 +3486,8 @@ describe("buildAfterTurnRuntimeContext", () => {
agentDir: "/tmp/agent", agentDir: "/tmp/agent",
}); });
expect(legacy).toMatchObject({ expect(legacy.provider).toBe("openai-codex");
provider: "openai-codex", expect(legacy.model).toBe("gpt-5.4");
model: "gpt-5.4",
});
}); });
it("resolves compaction.model override in runtime context so all context engines use the correct model", () => { it("resolves compaction.model override in runtime context so all context engines use the correct model", () => {
@@ -3558,12 +3523,10 @@ describe("buildAfterTurnRuntimeContext", () => {
// buildEmbeddedCompactionRuntimeContext now resolves the override eagerly // buildEmbeddedCompactionRuntimeContext now resolves the override eagerly
// so that context engines (including third-party ones) receive the correct // so that context engines (including third-party ones) receive the correct
// compaction model in the runtime context. // compaction model in the runtime context.
expect(legacy).toMatchObject({ expect(legacy.provider).toBe("openrouter");
provider: "openrouter", expect(legacy.model).toBe("anthropic/claude-sonnet-4-5");
model: "anthropic/claude-sonnet-4-5", // Auth profile dropped because provider changed from openai-codex to openrouter.
// Auth profile dropped because provider changed from openai-codex to openrouter expect(legacy.authProfileId).toBeUndefined();
authProfileId: undefined,
});
}); });
it("includes resolved auth profile fields for context-engine afterTurn compaction", () => { it("includes resolved auth profile fields for context-engine afterTurn compaction", () => {
const promptCache = buildContextEnginePromptCacheInfo({ const promptCache = buildContextEnginePromptCacheInfo({
@@ -3599,20 +3562,14 @@ describe("buildAfterTurnRuntimeContext", () => {
promptCache, promptCache,
}); });
expect(legacy).toMatchObject({ expect(legacy.authProfileId).toBe("openai:p1");
authProfileId: "openai:p1", expect(legacy.provider).toBe("openai-codex");
provider: "openai-codex", expect(legacy.model).toBe("gpt-5.4");
model: "gpt-5.4", expect(legacy.workspaceDir).toBe("/tmp/workspace");
workspaceDir: "/tmp/workspace", expect(legacy.agentDir).toBe("/tmp/agent");
agentDir: "/tmp/agent", expect(legacy.tokenBudget).toBe(1050000);
tokenBudget: 1050000, expect(legacy.currentTokenCount).toBe(52);
currentTokenCount: 52, expect(legacy.promptCache?.lastCallUsage?.total).toBe(57);
promptCache: {
lastCallUsage: {
total: 57,
},
},
});
}); });
it("derives afterTurn token count from the current assistant usage snapshot", () => { it("derives afterTurn token count from the current assistant usage snapshot", () => {
@@ -3648,14 +3605,8 @@ describe("buildAfterTurnRuntimeContext", () => {
promptCache, promptCache,
}); });
expect(legacy).toMatchObject({ expect(legacy.currentTokenCount).toBe(52);
currentTokenCount: 52, expect(legacy.promptCache?.lastCallUsage?.total).toBe(57);
promptCache: {
lastCallUsage: {
total: 57,
},
},
});
}); });
it("preserves sender and channel routing context for scoped compaction discovery", () => { it("preserves sender and channel routing context for scoped compaction discovery", () => {
@@ -3684,11 +3635,9 @@ describe("buildAfterTurnRuntimeContext", () => {
agentDir: "/tmp/agent", agentDir: "/tmp/agent",
}); });
expect(legacy).toMatchObject({ expect(legacy.senderId).toBe("user-123");
senderId: "user-123", expect(legacy.currentChannelId).toBe("C123");
currentChannelId: "C123", expect(legacy.currentThreadTs).toBe("thread-9");
currentThreadTs: "thread-9", expect(legacy.currentMessageId).toBe("msg-42");
currentMessageId: "msg-42",
});
}); });
}); });