mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
chore(tooling): enforce indexed access in core tests (#105375)
* test(tooling): enforce indexed access in core tests * test(tui): constrain theme environment overrides * test(doctor): enforce indexed access in migration fixtures
This commit is contained in:
committed by
GitHub
parent
161525d43c
commit
1b313dc4d4
@@ -1,4 +1,6 @@
|
||||
// Google shared conversion tests cover runtime-to-Google payload conversion.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Context, Tool } from "../types.js";
|
||||
import { convertMessages, convertTools } from "./google-shared.js";
|
||||
@@ -173,10 +175,10 @@ describe("google-shared convertMessages", () => {
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(2);
|
||||
expect(contents[0].role).toBe("user");
|
||||
expect(contents[1].role).toBe("user");
|
||||
expect(contents[0].parts).toHaveLength(1);
|
||||
expect(contents[1].parts).toHaveLength(1);
|
||||
expect(expectDefined(contents[0], "contents[0] test invariant").role).toBe("user");
|
||||
expect(expectDefined(contents[1], "contents[1] test invariant").role).toBe("user");
|
||||
expect(expectDefined(contents[0], "contents[0] test invariant").parts).toHaveLength(1);
|
||||
expect(expectDefined(contents[1], "contents[1] test invariant").parts).toHaveLength(1);
|
||||
}
|
||||
|
||||
it("keeps thinking blocks when provider/model match", () => {
|
||||
@@ -195,8 +197,8 @@ describe("google-shared convertMessages", () => {
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(1);
|
||||
expect(contents[0].role).toBe("model");
|
||||
const part = asRecord(contents[0].parts?.[0]);
|
||||
expect(expectDefined(contents[0], "contents[0] test invariant").role).toBe("model");
|
||||
const part = asRecord(expectDefined(contents[0], "contents[0] test invariant").parts?.[0]);
|
||||
expect(part.thought).toBe(true);
|
||||
expect(part.thoughtSignature).toBe("c2ln");
|
||||
});
|
||||
@@ -254,8 +256,8 @@ describe("google-shared convertMessages", () => {
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model"]);
|
||||
expect(contents[1].parts).toHaveLength(1);
|
||||
expect(contents[2].parts).toHaveLength(1);
|
||||
expect(expectDefined(contents[1], "contents[1] test invariant").parts).toHaveLength(1);
|
||||
expect(expectDefined(contents[2], "contents[2] test invariant").parts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("handles user message after tool result without model response in between", () => {
|
||||
@@ -291,16 +293,16 @@ describe("google-shared convertMessages", () => {
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(4);
|
||||
expect(contents[0].role).toBe("user");
|
||||
expect(contents[1].role).toBe("model");
|
||||
expect(contents[2].role).toBe("user");
|
||||
expect(contents[3].role).toBe("user");
|
||||
const toolResponsePart = contents[2].parts?.find(
|
||||
expect(expectDefined(contents[0], "contents[0] test invariant").role).toBe("user");
|
||||
expect(expectDefined(contents[1], "contents[1] test invariant").role).toBe("model");
|
||||
expect(expectDefined(contents[2], "contents[2] test invariant").role).toBe("user");
|
||||
expect(expectDefined(contents[3], "contents[3] test invariant").role).toBe("user");
|
||||
const toolResponsePart = expectDefined(contents[2], "contents[2] test invariant").parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
const toolResponse = asRecord(toolResponsePart);
|
||||
expect(requireRecordProperty(toolResponse, "functionResponse").name).toBe("myTool");
|
||||
expect(contents[3].role).toBe("user");
|
||||
expect(expectDefined(contents[3], "contents[3] test invariant").role).toBe("user");
|
||||
});
|
||||
|
||||
it("ensures function call comes after user turn, not after model turn", () => {
|
||||
@@ -325,7 +327,7 @@ describe("google-shared convertMessages", () => {
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model", "user"]);
|
||||
const toolCallPart = contents[2].parts?.find(
|
||||
const toolCallPart = expectDefined(contents[2], "contents[2] test invariant").parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Part } from "@google/genai";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Context, Model } from "../types.js";
|
||||
import { convertMessages } from "./google-shared.js";
|
||||
@@ -79,7 +80,9 @@ describe("google-shared convertMessages — parallel tool results with an image
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
|
||||
expect(contents.map((content) => content.role)).toEqual(["user", "model", "user", "user"]);
|
||||
expect(functionResponseNames(contents[2].parts)).toEqual(resultOrder);
|
||||
expect(
|
||||
functionResponseNames(expectDefined(contents[2], "contents[2] test invariant").parts),
|
||||
).toEqual(resultOrder);
|
||||
expect(contents[3]).toEqual({
|
||||
role: "user",
|
||||
parts: [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Gateway Protocol tests cover native protocol levels.guard behavior.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, it } from "vitest";
|
||||
import { ProtocolSchemas } from "./schema/protocol-schemas.js";
|
||||
import {
|
||||
@@ -47,7 +48,7 @@ function extractInteger(
|
||||
`${relativePath}: missing ${label}; keep native Gateway protocol levels in sync with packages/gateway-protocol/src/version.ts.`,
|
||||
);
|
||||
}
|
||||
return Number.parseInt(match[1], 10);
|
||||
return Number.parseInt(expectDefined(match[1], "match[1] test invariant"), 10);
|
||||
}
|
||||
|
||||
/** Compares native min/max values to the TypeScript version constants. */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Markdown Core tests cover frontmatter behavior.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import JSON5 from "json5";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseFrontmatterBlock, stripFrontmatterBlock } from "./frontmatter.js";
|
||||
@@ -33,7 +34,7 @@ metadata:
|
||||
const result = parseFrontmatterBlock(content);
|
||||
expect(result.metadata).toBe('{"openclaw":{"emoji":"disk","events":["command:new"]}}');
|
||||
|
||||
const parsed = JSON5.parse(result.metadata);
|
||||
const parsed = JSON5.parse(expectDefined(result.metadata, "result.metadata test invariant"));
|
||||
expect(parsed.openclaw?.emoji).toBe("disk");
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildFileEntry,
|
||||
@@ -251,7 +252,9 @@ describe("memory host SDK package internals", () => {
|
||||
|
||||
remapChunkLines(chunks, lineMap);
|
||||
|
||||
expect(chunks[0].startLine).toBe(4);
|
||||
expect(chunks[chunks.length - 1].endLine).toBe(13);
|
||||
expect(expectDefined(chunks[0], "chunks[0] test invariant").startLine).toBe(4);
|
||||
expect(
|
||||
expectDefined(chunks[chunks.length - 1], "chunks[chunks.length - 1] test invariant").endLine,
|
||||
).toBe(13);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Tests ACP session manager resolution, turn execution, state transitions, and cleanup. */
|
||||
import { setTimeout as scheduleNativeTimeout } from "node:timers";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -1345,7 +1346,10 @@ describe("AcpSessionManager", () => {
|
||||
sessionKey,
|
||||
});
|
||||
expect(runtimeState.prepareFreshSession.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
runtimeState.ensureSession.mock.invocationCallOrder[0],
|
||||
expectDefined(
|
||||
runtimeState.ensureSession.mock.invocationCallOrder[0],
|
||||
"runtimeState.ensureSession.mock.invocationCallOrder[0] test invariant",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Tests configured channel-to-ACP binding resolution and generated session keys. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
||||
import type { ChannelConfiguredBindingProvider, ChannelPlugin } from "../channels/plugins/types.js";
|
||||
@@ -52,6 +53,10 @@ const discordBindings: ChannelConfiguredBindingProvider = {
|
||||
},
|
||||
};
|
||||
|
||||
function matchGroup(match: RegExpExecArray, index: number, context: string): string {
|
||||
return expectDefined(match[index], context);
|
||||
}
|
||||
|
||||
function parseTelegramTopicConversationForTest(params: {
|
||||
conversationId: string;
|
||||
parentConversationId?: string;
|
||||
@@ -67,7 +72,8 @@ function parseTelegramTopicConversationForTest(params: {
|
||||
}
|
||||
const canonicalTopicMatch = /^(-[^:]+):topic:([^:]+)$/.exec(conversationId);
|
||||
if (canonicalTopicMatch) {
|
||||
const [, chatId, topicId] = canonicalTopicMatch;
|
||||
const chatId = matchGroup(canonicalTopicMatch, 1, "Telegram topic chat id");
|
||||
const topicId = matchGroup(canonicalTopicMatch, 2, "Telegram topic id");
|
||||
return {
|
||||
canonicalConversationId: `${chatId}:topic:${topicId}`,
|
||||
chatId,
|
||||
@@ -146,7 +152,9 @@ function parseFeishuConversationIdForTest(params: {
|
||||
|
||||
const topicSenderMatch = /^(.+):topic:([^:]+):sender:([^:]+)$/.exec(conversationId);
|
||||
if (topicSenderMatch) {
|
||||
const [, chatId, topicId, senderOpenId] = topicSenderMatch;
|
||||
const chatId = matchGroup(topicSenderMatch, 1, "Feishu topic-sender chat id");
|
||||
const topicId = matchGroup(topicSenderMatch, 2, "Feishu topic-sender topic id");
|
||||
const senderOpenId = matchGroup(topicSenderMatch, 3, "Feishu topic-sender open id");
|
||||
return {
|
||||
canonicalConversationId: `${chatId}:topic:${topicId}:sender:${senderOpenId}`,
|
||||
chatId,
|
||||
@@ -158,7 +166,8 @@ function parseFeishuConversationIdForTest(params: {
|
||||
|
||||
const topicMatch = /^(.+):topic:([^:]+)$/.exec(conversationId);
|
||||
if (topicMatch) {
|
||||
const [, chatId, topicId] = topicMatch;
|
||||
const chatId = matchGroup(topicMatch, 1, "Feishu topic chat id");
|
||||
const topicId = matchGroup(topicMatch, 2, "Feishu topic id");
|
||||
return {
|
||||
canonicalConversationId: `${chatId}:topic:${topicId}`,
|
||||
chatId,
|
||||
@@ -169,7 +178,8 @@ function parseFeishuConversationIdForTest(params: {
|
||||
|
||||
const senderMatch = /^(.+):sender:([^:]+)$/.exec(conversationId);
|
||||
if (senderMatch) {
|
||||
const [, chatId, senderOpenId] = senderMatch;
|
||||
const chatId = matchGroup(senderMatch, 1, "Feishu sender chat id");
|
||||
const senderOpenId = matchGroup(senderMatch, 2, "Feishu sender open id");
|
||||
return {
|
||||
canonicalConversationId: `${chatId}:sender:${senderOpenId}`,
|
||||
chatId,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Tests prompt cancellation scoping across concurrent ACP sessions and Gateway runs. */
|
||||
import type { CancelNotification, PromptRequest, PromptResponse } from "@agentclientprotocol/sdk";
|
||||
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
|
||||
/** Tests prompt cancellation scoping across concurrent ACP sessions and Gateway runs. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { EventFrame } from "../../packages/gateway-protocol/src/index.js";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
@@ -97,7 +98,7 @@ async function startPendingPrompt(
|
||||
});
|
||||
return {
|
||||
promptPromise,
|
||||
runId: harness.sentRunIds[before],
|
||||
runId: expectDefined(harness.sentRunIds[before], "harness.sentRunIds[before] test invariant"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/** Tests ACP translator initialize/session lifecycle and prompt bridge behavior. */
|
||||
import type {
|
||||
CloseSessionRequest,
|
||||
InitializeRequest,
|
||||
@@ -9,6 +8,8 @@ import type {
|
||||
} from "@agentclientprotocol/sdk";
|
||||
import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
||||
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
|
||||
/** Tests ACP translator initialize/session lifecycle and prompt bridge behavior. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
import type { GatewaySessionRow } from "../gateway/session-utils.js";
|
||||
@@ -132,7 +133,7 @@ async function startPendingPrompt(params: {
|
||||
});
|
||||
return {
|
||||
promptPromise,
|
||||
runId: params.sentRunIds[before],
|
||||
runId: expectDefined(params.sentRunIds[before], "params.sentRunIds[before] test invariant"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Tests ACP translator permission relay for Gateway exec approvals. */
|
||||
import type { CancelNotification } from "@agentclientprotocol/sdk";
|
||||
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
|
||||
/** Tests ACP translator permission relay for Gateway exec approvals. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { EventFrame } from "../../packages/gateway-protocol/src/index.js";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
@@ -302,7 +303,12 @@ describe("ACP translator permission relay", () => {
|
||||
expect(requestPermission).not.toHaveBeenCalled();
|
||||
expect(approvalResolveCalls(request)).toHaveLength(0);
|
||||
|
||||
await agent.handleGatewayEvent(createApprovalEvent({ runId: runIds[1], approvalId }));
|
||||
await agent.handleGatewayEvent(
|
||||
createApprovalEvent({
|
||||
runId: expectDefined(runIds[1], "runIds[1] test invariant"),
|
||||
approvalId,
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(requestPermission).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AcpInitializeSessionInput } from "../acp/control-plane/manager.types.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
@@ -882,9 +883,18 @@ describe("spawnAcpDirect", () => {
|
||||
const agentCallIndex = hoisted.callGatewayMock.mock.calls.findIndex(
|
||||
(call: unknown[]) => (call[0] as { method?: string }).method === "agent",
|
||||
);
|
||||
const patchCallOrder = hoisted.callGatewayMock.mock.invocationCallOrder[patchCallIndex];
|
||||
const initializeCallOrder = hoisted.initializeSessionMock.mock.invocationCallOrder[0];
|
||||
const agentCallOrder = hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex];
|
||||
const patchCallOrder = expectDefined(
|
||||
hoisted.callGatewayMock.mock.invocationCallOrder[patchCallIndex],
|
||||
"hoisted.callGatewayMock.mock.invocationCallOrder[patchCallIndex] test invariant",
|
||||
);
|
||||
const initializeCallOrder = expectDefined(
|
||||
hoisted.initializeSessionMock.mock.invocationCallOrder[0],
|
||||
"hoisted.initializeSessionMock.mock.invocationCallOrder[0] test invariant",
|
||||
);
|
||||
const agentCallOrder = expectDefined(
|
||||
hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex],
|
||||
"hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex] test invariant",
|
||||
);
|
||||
expect(typeof patchCallOrder).toBe("number");
|
||||
expect(typeof initializeCallOrder).toBe("number");
|
||||
expect(typeof agentCallOrder).toBe("number");
|
||||
@@ -2620,8 +2630,14 @@ describe("spawnAcpDirect", () => {
|
||||
const agentCallIndex = hoisted.callGatewayMock.mock.calls.findIndex(
|
||||
(call: unknown[]) => (call[0] as { method?: string }).method === "agent",
|
||||
);
|
||||
const relayCallOrder = hoisted.startAcpSpawnParentStreamRelayMock.mock.invocationCallOrder[0];
|
||||
const agentCallOrder = hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex];
|
||||
const relayCallOrder = expectDefined(
|
||||
hoisted.startAcpSpawnParentStreamRelayMock.mock.invocationCallOrder[0],
|
||||
"hoisted.startAcpSpawnParentStreamRelayMock.mock.invocationCallOrder[0] test invariant",
|
||||
);
|
||||
const agentCallOrder = expectDefined(
|
||||
hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex],
|
||||
"hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex] test invariant",
|
||||
);
|
||||
expect(agentCall?.params?.deliver).toBe(false);
|
||||
expect(typeof relayCallOrder).toBe("number");
|
||||
expect(typeof agentCallOrder).toBe("number");
|
||||
@@ -3065,10 +3081,15 @@ describe("spawnAcpDirect", () => {
|
||||
const agentCallIndex = hoisted.callGatewayMock.mock.calls.findIndex(
|
||||
(call: unknown[]) => (call[0] as { method?: string }).method === "agent",
|
||||
);
|
||||
const agentCallOrder = hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex];
|
||||
const agentCallOrder = expectDefined(
|
||||
hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex],
|
||||
"hoisted.callGatewayMock.mock.invocationCallOrder[agentCallIndex] test invariant",
|
||||
);
|
||||
expect(typeof agentCallOrder).toBe("number");
|
||||
expect(typeof notifyOrder[0]).toBe("number");
|
||||
expect(notifyOrder[0] > agentCallOrder).toBe(true);
|
||||
expect(expectDefined(notifyOrder[0], "notifyOrder[0] test invariant") > agentCallOrder).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("binds Telegram forum-topic ACP sessions to the current topic", async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import http from "node:http";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanupTempDirs, makeTempDir } from "../../test/helpers/temp-dir.js";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
@@ -1894,7 +1895,7 @@ process.on("SIGINT", shutdown);`,
|
||||
},
|
||||
});
|
||||
const toolsA = await materializeBundleMcpToolsForRun({ runtime: runtimeA });
|
||||
const resultA = await toolsA.tools[0].execute(
|
||||
const resultA = await expectDefined(toolsA.tools[0], "toolsA.tools[0] test invariant").execute(
|
||||
"call-configured-probe-a",
|
||||
{},
|
||||
undefined,
|
||||
@@ -1920,7 +1921,7 @@ process.on("SIGINT", shutdown);`,
|
||||
},
|
||||
});
|
||||
const toolsB = await materializeBundleMcpToolsForRun({ runtime: runtimeB });
|
||||
const resultB = await toolsB.tools[0].execute(
|
||||
const resultB = await expectDefined(toolsB.tools[0], "toolsB.tools[0] test invariant").execute(
|
||||
"call-configured-probe-b",
|
||||
{},
|
||||
undefined,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** Tests materializing MCP catalog tools into agent tool definitions and results. */
|
||||
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { validateToolArguments } from "openclaw/plugin-sdk/llm";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getPluginToolMeta } from "../plugins/tools.js";
|
||||
@@ -170,7 +172,10 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
const materialized = await materializeBundleMcpToolsForRun({ runtime: sessionRuntime });
|
||||
materialized.restrictAppTools?.(materialized.tools);
|
||||
|
||||
const result = await materialized.tools[0].execute("call-1", {}, undefined, undefined);
|
||||
const result = await expectDefined(
|
||||
materialized.tools[0],
|
||||
"materialized.tools[0] test invariant",
|
||||
).execute("call-1", {}, undefined, undefined);
|
||||
expect(result.content).toEqual([{ type: "image", data: "aW1hZ2U=", mimeType: "image/png" }]);
|
||||
expect(result.details).toMatchObject({
|
||||
mcpAppPreview: { mcpApp: { viewId: "cv_app" } },
|
||||
@@ -186,8 +191,12 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
});
|
||||
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["bundleProbe__bundle_probe"]);
|
||||
expect(runtime.tools[0].executionMode).toBe("sequential");
|
||||
expect(getPluginToolMeta(runtime.tools[0])).toMatchObject({
|
||||
expect(expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").executionMode).toBe(
|
||||
"sequential",
|
||||
);
|
||||
expect(
|
||||
getPluginToolMeta(expectDefined(runtime.tools[0], "runtime.tools[0] test invariant")),
|
||||
).toMatchObject({
|
||||
pluginId: "bundle-mcp",
|
||||
mcp: {
|
||||
serverName: "bundleProbe",
|
||||
@@ -196,7 +205,12 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
operation: "tool",
|
||||
},
|
||||
});
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
const result = await expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").execute(
|
||||
"call-bundle-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expectTextContentBlock(result.content[0], "FROM-BUNDLE");
|
||||
expect(result.details).toEqual({
|
||||
mcpServer: "bundleProbe",
|
||||
@@ -211,7 +225,9 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(runtime.tools[0].executionMode).toBe("parallel");
|
||||
expect(expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").executionMode).toBe(
|
||||
"parallel",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps structuredContent visible when MCP tools also return text content", async () => {
|
||||
@@ -228,7 +244,12 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
const result = await expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").execute(
|
||||
"call-bundle-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expectTextContentBlock(
|
||||
result.content[0],
|
||||
@@ -288,7 +309,12 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
const result = await expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").execute(
|
||||
"call-bundle-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.content).toEqual([
|
||||
{ type: "text", text: "intro" },
|
||||
@@ -312,7 +338,12 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
const result = await expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").execute(
|
||||
"call-bundle-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.content).toHaveLength(1);
|
||||
expect(result.content[0]).toEqual({ type: "text", text: JSON.stringify({ type: "image" }) });
|
||||
@@ -483,9 +514,14 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
"knowledge__resources_list",
|
||||
"knowledge__resources_read",
|
||||
]);
|
||||
await expect(tools[0].execute("inventory-only", {}, undefined, undefined)).rejects.toThrow(
|
||||
"bundle-mcp catalog projection cannot execute tools",
|
||||
);
|
||||
await expect(
|
||||
expectDefined(tools[0], "tools[0] test invariant").execute(
|
||||
"inventory-only",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
).rejects.toThrow("bundle-mcp catalog projection cannot execute tools");
|
||||
});
|
||||
|
||||
it("materializes configured MCP tools through the session runtime boundary", async () => {
|
||||
@@ -517,13 +553,21 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
});
|
||||
|
||||
expect(created).toHaveLength(1);
|
||||
expect(created[0].sessionId).toMatch(/^bundle-mcp:/);
|
||||
expect(created[0].workspaceDir).toBe("/workspace");
|
||||
expect(created[0].cfg?.mcp?.servers?.configuredProbe?.command).toBe("node");
|
||||
expect(created[0].cfg?.mcp?.servers?.configuredProbe?.args).toEqual(["configured-probe.mjs"]);
|
||||
expect(expectDefined(created[0], "created[0] test invariant").sessionId).toMatch(
|
||||
/^bundle-mcp:/,
|
||||
);
|
||||
expect(expectDefined(created[0], "created[0] test invariant").workspaceDir).toBe("/workspace");
|
||||
expect(
|
||||
expectDefined(created[0], "created[0] test invariant").cfg?.mcp?.servers?.configuredProbe
|
||||
?.command,
|
||||
).toBe("node");
|
||||
expect(
|
||||
expectDefined(created[0], "created[0] test invariant").cfg?.mcp?.servers?.configuredProbe
|
||||
?.args,
|
||||
).toEqual(["configured-probe.mjs"]);
|
||||
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["configuredProbe__bundle_probe"]);
|
||||
const result = await runtime.tools[0].execute(
|
||||
const result = await expectDefined(runtime.tools[0], "runtime.tools[0] test invariant").execute(
|
||||
"call-configured-probe",
|
||||
{},
|
||||
undefined,
|
||||
@@ -634,7 +678,7 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
},
|
||||
});
|
||||
expect(
|
||||
validateToolArguments(runtime.tools[0], {
|
||||
validateToolArguments(expectDefined(runtime.tools[0], "runtime.tools[0] test invariant"), {
|
||||
type: "toolCall",
|
||||
id: "call-page",
|
||||
name: "notion__API-post-page",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests live model switching behavior in active agent command sessions. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { INTERNAL_RUNTIME_CONTEXT_BEGIN, INTERNAL_RUNTIME_CONTEXT_END } from "./internal-events.js";
|
||||
@@ -651,9 +653,13 @@ vi.mock("./model-selection.js", () => {
|
||||
if (!alias) {
|
||||
continue;
|
||||
}
|
||||
const [provider, ...modelParts] = ref.split("/");
|
||||
const [rawProvider, ...modelParts] = ref.split("/");
|
||||
const provider = expectDefined(rawProvider, `provider in model ref ${ref}`);
|
||||
const model = modelParts.join("/");
|
||||
byAlias.set(alias.toLowerCase(), { alias, ref: { provider, model } });
|
||||
byAlias.set(alias.toLowerCase(), {
|
||||
alias,
|
||||
ref: { provider, model },
|
||||
});
|
||||
byKey.set(`${provider}/${model}`, [alias]);
|
||||
}
|
||||
return { byAlias, byKey };
|
||||
@@ -4053,9 +4059,10 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
|
||||
state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => {
|
||||
state.persistSessionEntryMock.mockClear();
|
||||
const result = await params.run("openai", "claude");
|
||||
const currentEntry = (state.sessionStoreMock as Record<string, SessionEntry>)[
|
||||
"agent:main:main"
|
||||
];
|
||||
const currentEntry = expectDefined(
|
||||
(state.sessionStoreMock as Record<string, SessionEntry>)["agent:main:main"],
|
||||
'(state.sessionStoreMock as Record<string, SessionEntry>)[ "agent:main... test invariant',
|
||||
);
|
||||
currentEntry.modelSelectionLocked = true;
|
||||
state.isModelSelectionLockedMock.mockReturnValue(true);
|
||||
return {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentTool } from "openclaw/plugin-sdk/agent-core";
|
||||
import { Type } from "typebox";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
@@ -104,7 +105,7 @@ describe("agent tool definition adapter", () => {
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
const missingWorkdir = path.join(os.tmpdir(), `openclaw-missing-denied-cwd-${Date.now()}`);
|
||||
|
||||
const existing = await definition.execute(
|
||||
const existing = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-denied-existing-cwd",
|
||||
{
|
||||
command: "echo denied",
|
||||
@@ -114,7 +115,7 @@ describe("agent tool definition adapter", () => {
|
||||
undefined,
|
||||
extensionContext,
|
||||
);
|
||||
const missing = await definition.execute(
|
||||
const missing = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-denied-missing-cwd",
|
||||
{
|
||||
command: "echo denied",
|
||||
@@ -150,7 +151,7 @@ describe("agent tool definition adapter", () => {
|
||||
});
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-denied-backend-cwd",
|
||||
{
|
||||
command: "echo denied",
|
||||
@@ -175,7 +176,7 @@ describe("agent tool definition adapter", () => {
|
||||
});
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-malformed-exec-params",
|
||||
"not-an-object",
|
||||
undefined,
|
||||
@@ -205,7 +206,7 @@ describe("agent tool definition adapter", () => {
|
||||
});
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-malformed-backend-sandbox-exec-params",
|
||||
"not-an-object",
|
||||
undefined,
|
||||
@@ -229,7 +230,7 @@ describe("agent tool definition adapter", () => {
|
||||
});
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-malformed-elevated-exec-params",
|
||||
{},
|
||||
undefined,
|
||||
@@ -259,7 +260,7 @@ describe("agent tool definition adapter", () => {
|
||||
});
|
||||
const [definition] = toToolDefinitions([tool]);
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-malformed-backend-sandbox-exec-params",
|
||||
{
|
||||
workdir: "/remote/workspace/generated",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Ensures gateway approval requests use non-blocking semantics and preserve
|
||||
* plugin hook decisions.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
|
||||
import { setEmbeddedMode } from "../infra/embedded-mode.js";
|
||||
@@ -185,7 +187,10 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
|
||||
await vi.waitFor(() => {
|
||||
expect(broker.listPending()).toHaveLength(1);
|
||||
});
|
||||
const approval = broker.listPending()[0];
|
||||
const approval = expectDefined(
|
||||
broker.listPending()[0],
|
||||
"broker.listPending()[0] test invariant",
|
||||
);
|
||||
expect(approval?.request.toolName).toBe("skill_workshop");
|
||||
expect(broker.resolve(approval?.id, "allow-once")).toBe(true);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { replaceSessionEntry } from "../config/sessions/session-accessor.js";
|
||||
@@ -339,7 +340,7 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
agentId: "main",
|
||||
sessionKey: "main",
|
||||
});
|
||||
const [def] = toToolDefinitions([wrapped]);
|
||||
const def = expectDefined(toToolDefinitions([wrapped])[0], "wrapped web-fetch definition");
|
||||
const extensionContext = {} as Parameters<typeof def.execute>[4];
|
||||
await def.execute(
|
||||
"call-dedup",
|
||||
@@ -980,7 +981,7 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
sessionKey: "main",
|
||||
});
|
||||
const withAbort = wrapToolWithAbortSignal(wrapped, abortController.signal);
|
||||
const [def] = toToolDefinitions([withAbort]);
|
||||
const def = expectDefined(toToolDefinitions([withAbort])[0], "abort-wrapped Bash definition");
|
||||
const extensionContext = {} as Parameters<typeof def.execute>[4];
|
||||
|
||||
await def.execute(
|
||||
@@ -1010,12 +1011,15 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
text: `Fetched with status ${(result.details as { status: number }).status}`,
|
||||
}),
|
||||
);
|
||||
const tool = wrapToolWithBeforeToolCallHook(
|
||||
normalizeToolParameters(sourceTool, { modelProvider: "openai" }),
|
||||
{
|
||||
sessionId: "session-terminal-presentation",
|
||||
onToolOutcome,
|
||||
},
|
||||
const tool = expectDefined(
|
||||
wrapToolWithBeforeToolCallHook(
|
||||
normalizeToolParameters(sourceTool, { modelProvider: "openai" }),
|
||||
{
|
||||
sessionId: "session-terminal-presentation",
|
||||
onToolOutcome,
|
||||
},
|
||||
),
|
||||
"wrapToolWithBeforeToolCallHook( normalizeToolParameters(sourceTool, {... test invariant",
|
||||
);
|
||||
await tool.execute("call-terminal-presentation", {
|
||||
url: "https://example.com",
|
||||
@@ -1116,13 +1120,16 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
it("passes hook context for unwrapped tool definitions", async () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
const baseTool = { name: "exec", execute, description: "exec", parameters: {} } as any;
|
||||
const [def] = toToolDefinitions([baseTool], {
|
||||
agentId: "code-agent",
|
||||
sessionKey: "agent:code-agent:main",
|
||||
sessionId: "session-code",
|
||||
runId: "run-code",
|
||||
channelId: "channel-code",
|
||||
});
|
||||
const def = expectDefined(
|
||||
toToolDefinitions([baseTool], {
|
||||
agentId: "code-agent",
|
||||
sessionKey: "agent:code-agent:main",
|
||||
sessionId: "session-code",
|
||||
runId: "run-code",
|
||||
channelId: "channel-code",
|
||||
})[0],
|
||||
"unwrapped exec definition",
|
||||
);
|
||||
const extensionContext = {} as Parameters<typeof def.execute>[4];
|
||||
|
||||
await def.execute(
|
||||
@@ -1178,7 +1185,7 @@ describe("before_tool_call hook integration for client tools", () => {
|
||||
runBeforeToolCallImpl: async () => ({ params: { extra: true } }),
|
||||
});
|
||||
const onClientToolCall = vi.fn();
|
||||
const [tool] = toClientToolDefinitions(
|
||||
const clientTools = toClientToolDefinitions(
|
||||
[
|
||||
{
|
||||
type: "function",
|
||||
@@ -1192,6 +1199,7 @@ describe("before_tool_call hook integration for client tools", () => {
|
||||
onClientToolCall,
|
||||
{ agentId: "main", sessionKey: "main" },
|
||||
);
|
||||
const tool = expectDefined(clientTools[0], "client tool definition");
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[4];
|
||||
await tool.execute("client-call-1", { value: "ok" }, undefined, undefined, extensionContext);
|
||||
|
||||
@@ -1360,7 +1368,7 @@ describe("before_tool_call hook integration for client tools", () => {
|
||||
value: { gate: "client" },
|
||||
});
|
||||
|
||||
const [tool] = toClientToolDefinitions(
|
||||
const clientTools = toClientToolDefinitions(
|
||||
[
|
||||
{
|
||||
type: "function",
|
||||
@@ -1379,6 +1387,7 @@ describe("before_tool_call hook integration for client tools", () => {
|
||||
config: config as never,
|
||||
},
|
||||
);
|
||||
const tool = expectDefined(clientTools[0], "client tool definition");
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[4];
|
||||
await tool.execute("client-call-policy", {}, undefined, undefined, extensionContext);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Tests cron-aware deferred follow-up guidance in exec/process descriptions.
|
||||
* Protects the model-facing text selected after tool filtering.
|
||||
*/
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getPluginToolMeta, setPluginToolMeta } from "../plugins/tools.js";
|
||||
import { applyDeferredFollowupToolDescriptions } from "./agent-tools.deferred-followup.js";
|
||||
@@ -59,7 +60,10 @@ describe("createOpenClawCodingTools deferred follow-up guidance", () => {
|
||||
const [updated] = applyDeferredFollowupToolDescriptions([processTool]);
|
||||
|
||||
expect(updated).not.toBe(processTool);
|
||||
expect(getPluginToolMeta(updated)).toEqual({ pluginId: "example", optional: false });
|
||||
expect(getPluginToolMeta(expectDefined(updated, "updated test invariant"))).toEqual({
|
||||
pluginId: "example",
|
||||
optional: false,
|
||||
});
|
||||
expect(getChannelAgentToolMeta(updated as never)).toEqual({
|
||||
channelId: "example-channel",
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { normalizeToolParameterSchema } from "@openclaw/ai/internal/openai";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
/**
|
||||
* Tests provider-compatible tool schema normalization.
|
||||
* Protects caching, ref inlining, OpenAPI keyword cleanup, and no-parameter
|
||||
@@ -1085,13 +1086,16 @@ describe("normalizeToolParameters", () => {
|
||||
required?: string[];
|
||||
properties?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
const properties = expectDefined(parameters.properties, "normalized schema properties");
|
||||
const count = expectDefined(properties.count, "normalized count property");
|
||||
const query = expectDefined(properties.query, "normalized query property");
|
||||
|
||||
expect(parameters.required).toEqual(["count"]);
|
||||
expect(parameters.properties?.count.minimum).toBeUndefined();
|
||||
expect(parameters.properties?.count.maximum).toBeUndefined();
|
||||
expect(parameters.properties?.count.type).toBe("integer");
|
||||
expect(parameters.properties?.query.minLength).toBeUndefined();
|
||||
expect(parameters.properties?.query.type).toBe("string");
|
||||
expect(count.minimum).toBeUndefined();
|
||||
expect(count.maximum).toBeUndefined();
|
||||
expect(count.type).toBe("integer");
|
||||
expect(query.minLength).toBeUndefined();
|
||||
expect(query.type).toBe("string");
|
||||
});
|
||||
|
||||
it("omits empty array items when model compat requires it", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Covers request construction, SSE parsing, aborts, tool calls, usage, and
|
||||
* provider transport hooks.
|
||||
*/
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { Model } from "openclaw/plugin-sdk/llm";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { attachModelProviderRequestTransport } from "./provider-request-config.js";
|
||||
@@ -3159,9 +3160,10 @@ describe("anthropic transport stream", () => {
|
||||
],
|
||||
},
|
||||
]);
|
||||
const [[url, fetchOptions]] = guardedFetchMock.mock.calls as unknown as Array<
|
||||
[string, { method?: string }]
|
||||
>;
|
||||
const [url, fetchOptions] = expectDefined(
|
||||
(guardedFetchMock.mock.calls as unknown as Array<[string, { method?: string }]>)[0],
|
||||
"(guardedFetchMock.mock.calls as unknown as Array<[string, { method?: string }]>)[0] test invariant",
|
||||
);
|
||||
expect(url).toBe("https://api.minimax.io/anthropic/v1/messages");
|
||||
expect(fetchOptions.method).toBe("POST");
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Protects the recovery path that adopts a winner's fresh token instead of
|
||||
* failing over after concurrent refresh races.
|
||||
*/
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
makeSeededRandom,
|
||||
@@ -107,7 +108,10 @@ describe("isRefreshTokenReusedError", () => {
|
||||
"already been used to generate a new access token",
|
||||
];
|
||||
for (let i = 0; i < 500; i += 1) {
|
||||
const marker = randomlyCased(markers[i % markers.length], rng);
|
||||
const marker = randomlyCased(
|
||||
expectDefined(markers[i % markers.length], "markers[i % markers.length] test invariant"),
|
||||
rng,
|
||||
);
|
||||
const prefix = randomJunk(rng, 64);
|
||||
const suffix = randomJunk(rng, 64);
|
||||
const msg = `${prefix}${marker}${suffix}`;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Covers runtime-only provenance, cloned store isolation, and stale credential
|
||||
* replacement decisions.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { MAX_DATE_TIMESTAMP_MS } from "../../shared/number-coercion.js";
|
||||
import {
|
||||
@@ -50,8 +52,11 @@ describe("overlayRuntimeExternalOAuthProfiles", () => {
|
||||
expect(overlaidCodexProfile.access).toBe("access-1");
|
||||
expect(store.profiles["openai:default"]?.type).toBe("api_key");
|
||||
|
||||
overlaid.profiles["openai:default"].provider = "mutated";
|
||||
overlaid.order!.openai.push("mutated");
|
||||
expectDefined(
|
||||
overlaid.profiles["openai:default"],
|
||||
'overlaid.profiles["openai:default"] test invariant',
|
||||
).provider = "mutated";
|
||||
expectDefined(overlaid.order?.openai, "OpenAI profile order").push("mutated");
|
||||
|
||||
expect(store.profiles["openai:default"]?.provider).toBe("openai");
|
||||
expect(store.order?.openai).toEqual(["openai:default"]);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
|
||||
import { captureEnv } from "../../test-utils/env.js";
|
||||
@@ -139,12 +140,15 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
|
||||
// Sub-agent store must NOT have been overwritten with main's foreign cred.
|
||||
const subRaw = readAuthProfileStoreForTest(subAgentDir);
|
||||
expectPersistedOpenAICodexProfile(subRaw.profiles[profileId], {
|
||||
access: "sub-own-access",
|
||||
refresh: "sub-own-refresh",
|
||||
accountId: "acct-sub",
|
||||
expires: subExpiry,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(subRaw.profiles[profileId], "subRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "sub-own-access",
|
||||
refresh: "sub-own-refresh",
|
||||
accountId: "acct-sub",
|
||||
expires: subExpiry,
|
||||
},
|
||||
);
|
||||
expect(JSON.stringify(subRaw)).not.toContain("main-foreign-access");
|
||||
});
|
||||
|
||||
@@ -212,12 +216,15 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
// Main must still hold its foreign cred, untouched (mirror would also
|
||||
// refuse because of identity mismatch).
|
||||
const mainRaw = readAuthProfileStoreForTest(mainAgentDir);
|
||||
expectPersistedOpenAICodexProfile(mainRaw.profiles[profileId], {
|
||||
access: "main-foreign-access",
|
||||
refresh: "main-foreign-refresh",
|
||||
accountId: "acct-other",
|
||||
expires: freshExpiry,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(mainRaw.profiles[profileId], "mainRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "main-foreign-access",
|
||||
refresh: "main-foreign-refresh",
|
||||
accountId: "acct-other",
|
||||
expires: freshExpiry,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("catch-block main-inherit refuses across accountId mismatch and surfaces the original error", async () => {
|
||||
@@ -288,11 +295,14 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
|
||||
// Sub-agent store must still have its own stale cred \u2014 no leak.
|
||||
const subRaw = readAuthProfileStoreForTest(subAgentDir);
|
||||
expectPersistedOpenAICodexProfile(subRaw.profiles[profileId], {
|
||||
access: "sub-stale",
|
||||
refresh: "sub-refresh-token",
|
||||
accountId: "acct-sub",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(subRaw.profiles[profileId], "subRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "sub-stale",
|
||||
refresh: "sub-refresh-token",
|
||||
accountId: "acct-sub",
|
||||
},
|
||||
);
|
||||
expect(JSON.stringify(subRaw)).not.toContain("main-foreign-refreshed");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
|
||||
import { captureEnv } from "../../test-utils/env.js";
|
||||
@@ -136,12 +137,15 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
// Main store should now carry refreshed metadata, so a peer agent
|
||||
// starting fresh can resolve the runtime credential without token races.
|
||||
const mainRaw = readAuthProfileStoreForTest(mainAgentDir);
|
||||
expectPersistedOpenAICodexProfile(mainRaw.profiles[profileId], {
|
||||
access: "sub-refreshed-access",
|
||||
refresh: "sub-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
accountId,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(mainRaw.profiles[profileId], "mainRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "sub-refreshed-access",
|
||||
refresh: "sub-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
accountId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("does not mirror when refresh was performed from the main agent itself", async () => {
|
||||
@@ -176,11 +180,14 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
|
||||
expect(result?.apiKey).toBe("main-refreshed-access");
|
||||
const mainRaw = readAuthProfileStoreForTest(mainAgentDir);
|
||||
expectPersistedOpenAICodexProfile(mainRaw.profiles[profileId], {
|
||||
access: "main-refreshed-access",
|
||||
refresh: "main-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(mainRaw.profiles[profileId], "mainRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "main-refreshed-access",
|
||||
refresh: "main-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
},
|
||||
);
|
||||
expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -346,20 +353,26 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
const subRaw = readAuthProfileStoreForTest(subAgentDir);
|
||||
expectPersistedOpenAICodexProfile(subRaw.profiles[profileId], {
|
||||
access: "local-stale-access",
|
||||
refresh: "local-stale-refresh",
|
||||
expires: now - 120_000,
|
||||
accountId,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(subRaw.profiles[profileId], "subRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "local-stale-access",
|
||||
refresh: "local-stale-refresh",
|
||||
expires: now - 120_000,
|
||||
accountId,
|
||||
},
|
||||
);
|
||||
|
||||
const mainRaw = readAuthProfileStoreForTest(mainAgentDir);
|
||||
expectPersistedOpenAICodexProfile(mainRaw.profiles[profileId], {
|
||||
access: "main-owner-refreshed-access",
|
||||
refresh: "main-owner-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
accountId,
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(mainRaw.profiles[profileId], "mainRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "main-owner-refreshed-access",
|
||||
refresh: "main-owner-refreshed-refresh",
|
||||
expires: freshExpiry,
|
||||
accountId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("inherits main-agent credentials via the catch-block fallback when refresh throws after main becomes fresh", async () => {
|
||||
@@ -425,11 +438,14 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
|
||||
// Sub-agent's store keeps its local expired credential; inherited OAuth is read-through.
|
||||
const subRaw = readAuthProfileStoreForTest(subAgentDir);
|
||||
expectPersistedOpenAICodexProfile(subRaw.profiles[profileId], {
|
||||
access: "cached-access-token",
|
||||
refresh: "refresh-token",
|
||||
accountId: "acct-shared",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(subRaw.profiles[profileId], "subRaw.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "cached-access-token",
|
||||
refresh: "refresh-token",
|
||||
accountId: "acct-shared",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("does not satisfy forced refresh from unchanged main-agent credentials after refresh fails", async () => {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { FILE_LOCK_TIMEOUT_ERROR_CODE, resetFileLockStateForTest } from "../../infra/file-lock.js";
|
||||
import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
|
||||
@@ -382,11 +383,14 @@ describe("resolveApiKeyForProfile openai refresh fallback", () => {
|
||||
});
|
||||
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expectPersistedOpenAICodexProfile(persisted.profiles[profileId], {
|
||||
access: "rotated-access-token",
|
||||
refresh: "rotated-refresh-token",
|
||||
accountId: "acct-rotated",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(persisted.profiles[profileId], "persisted.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "rotated-access-token",
|
||||
refresh: "rotated-refresh-token",
|
||||
accountId: "acct-rotated",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("refreshes imported Codex credentials into the canonical auth store without writing back to .codex", async () => {
|
||||
@@ -435,11 +439,14 @@ describe("resolveApiKeyForProfile openai refresh fallback", () => {
|
||||
email: undefined,
|
||||
});
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expectPersistedOpenAICodexProfile(persisted.profiles[profileId], {
|
||||
access: "rotated-cli-access-token",
|
||||
refresh: "rotated-cli-refresh-token",
|
||||
accountId: "acct-rotated",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(persisted.profiles[profileId], "persisted.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "rotated-cli-access-token",
|
||||
refresh: "rotated-cli-refresh-token",
|
||||
accountId: "acct-rotated",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores mismatched fresh Codex CLI credentials when canonical local auth is bound to another account", async () => {
|
||||
@@ -492,11 +499,14 @@ describe("resolveApiKeyForProfile openai refresh fallback", () => {
|
||||
});
|
||||
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expectPersistedOpenAICodexProfile(persisted.profiles[profileId], {
|
||||
access: "fresh-local-access-token",
|
||||
refresh: "fresh-local-refresh-token",
|
||||
accountId: "acct-local",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(persisted.profiles[profileId], "persisted.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "fresh-local-access-token",
|
||||
refresh: "fresh-local-refresh-token",
|
||||
accountId: "acct-local",
|
||||
},
|
||||
);
|
||||
const persistedProfile = requireOAuthProfile(persisted, profileId);
|
||||
expect(persistedProfile.accountId).toBe("acct-local");
|
||||
});
|
||||
@@ -554,10 +564,13 @@ describe("resolveApiKeyForProfile openai refresh fallback", () => {
|
||||
});
|
||||
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expectPersistedOpenAICodexProfile(persisted.profiles[profileId], {
|
||||
access: "fresh-access-token",
|
||||
refresh: "fresh-refresh-token",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(persisted.profiles[profileId], "persisted.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "fresh-access-token",
|
||||
refresh: "fresh-refresh-token",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("does not use same-account Codex CLI credentials after forced local refresh fails", async () => {
|
||||
@@ -1149,10 +1162,13 @@ describe("resolveApiKeyForProfile openai refresh fallback", () => {
|
||||
|
||||
expect(getOAuthApiKeyMock).toHaveBeenCalledTimes(2);
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expectPersistedOpenAICodexProfile(persisted.profiles[profileId], {
|
||||
access: "retried-access-token",
|
||||
refresh: "retried-refresh-token",
|
||||
});
|
||||
expectPersistedOpenAICodexProfile(
|
||||
expectDefined(persisted.profiles[profileId], "persisted.profiles[profileId] test invariant"),
|
||||
{
|
||||
access: "retried-access-token",
|
||||
refresh: "retried-refresh-token",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps throwing for non-codex providers on the same refresh error", async () => {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Regression coverage for process-local auth profile snapshots.
|
||||
* Verifies snapshots are cloned and isolated across agent-specific stores.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearRuntimeAuthProfileStoreSnapshots,
|
||||
@@ -58,15 +60,23 @@ describe("runtime auth profile snapshots", () => {
|
||||
try {
|
||||
const stored = createStore("access-1");
|
||||
setRuntimeAuthProfileStoreSnapshot(stored, agentDir);
|
||||
stored.profiles["openai:default"].provider = "mutated";
|
||||
stored.order!["openai"].push("mutated");
|
||||
expectDefined(
|
||||
stored.profiles["openai:default"],
|
||||
'stored.profiles["openai:default"] test invariant',
|
||||
).provider = "mutated";
|
||||
expectDefined(stored.order?.openai, "stored OpenAI profile order").push("mutated");
|
||||
|
||||
const first = getRuntimeAuthProfileStoreSnapshot(agentDir);
|
||||
expectOpenAICodexSnapshotCredential(first, { access: "access-1" });
|
||||
expect(first?.order?.["openai"]).toEqual(["openai:default"]);
|
||||
|
||||
first!.profiles["openai:default"].provider = "mutated-again";
|
||||
first!.usageStats!["openai:default"].lastUsed = 99;
|
||||
const firstSnapshot = expectDefined(first, "first auth profile snapshot");
|
||||
expectDefined(firstSnapshot.profiles["openai:default"], "first OpenAI profile").provider =
|
||||
"mutated-again";
|
||||
expectDefined(
|
||||
firstSnapshot.usageStats?.["openai:default"],
|
||||
"first OpenAI usage stats",
|
||||
).lastUsed = 99;
|
||||
|
||||
const second = getRuntimeAuthProfileStoreSnapshot(agentDir);
|
||||
expectOpenAICodexSnapshotCredential(second, { access: "access-1" });
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* auto-review, and follow-up execution paths.
|
||||
*/
|
||||
import crypto from "node:crypto";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ExecAllowlistEntry } from "../infra/exec-approvals.types.js";
|
||||
import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../utils/timer-delay.js";
|
||||
@@ -288,7 +289,7 @@ const resolveNodeIdFromListMock = vi.hoisted(() =>
|
||||
vi.fn((nodes: Array<{ nodeId: string; displayName?: string }>, query?: string) => {
|
||||
if (!query) {
|
||||
if (nodes.length === 1) {
|
||||
return nodes[0].nodeId;
|
||||
return expectDefined(nodes[0], "nodes[0] test invariant").nodeId;
|
||||
}
|
||||
throw new Error("node required");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Covers target resolution, cursor mode tracking, exit outcome classification,
|
||||
* system events, and process lifecycle behavior.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayActiveWorkInspectors } from "../infra/gateway-active-work.js";
|
||||
import type { RunExit } from "../process/supervisor/types.js";
|
||||
@@ -872,7 +874,10 @@ describe("runExecProcess POSIX command wrapper", () => {
|
||||
void ignoredRun;
|
||||
|
||||
expect(supervisorMock.spawn).toHaveBeenCalledTimes(1);
|
||||
const spawnCall = supervisorMock.spawn.mock.calls[0][0];
|
||||
const spawnCall = expectDefined(
|
||||
supervisorMock.spawn.mock.calls[0],
|
||||
"supervisorMock.spawn.mock.calls[0] test invariant",
|
||||
)[0];
|
||||
|
||||
const commandStr = spawnCall.argv.join(" ");
|
||||
expect(commandStr).toContain(
|
||||
@@ -916,7 +921,10 @@ describe("runExecProcess POSIX command wrapper", () => {
|
||||
void ignoredRun;
|
||||
|
||||
expect(supervisorMock.spawn).toHaveBeenCalledTimes(1);
|
||||
const spawnCall = supervisorMock.spawn.mock.calls[0][0];
|
||||
const spawnCall = expectDefined(
|
||||
supervisorMock.spawn.mock.calls[0],
|
||||
"supervisorMock.spawn.mock.calls[0] test invariant",
|
||||
)[0];
|
||||
|
||||
const commandStr = spawnCall.argv.join(" ");
|
||||
expect(commandStr).not.toContain("export PATH=");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Verifies plugin-provided env values are filtered and forwarded to the chosen
|
||||
* exec host without leaking unsafe overrides.
|
||||
*/
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
|
||||
import type { ExecuteNodeHostCommandParams } from "./bash-tools.exec-host-node.types.js";
|
||||
@@ -403,7 +404,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-invalid-wrapped-cwd-before-hooks",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -449,7 +450,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-backend-cwd-vetoed-before-validation",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -498,7 +499,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-backend-invalid-cwd-before-env",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -552,7 +553,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
channelId: "ctx-channel",
|
||||
});
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-backend-deferred-env-context",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -604,7 +605,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
channelId: "chat-1",
|
||||
});
|
||||
|
||||
const result = await definition.execute(
|
||||
const result = await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-invalid-lazy-cwd-before-hooks",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -667,7 +668,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
channelId: "chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-before",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -715,7 +716,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
channelId: "chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-lazy",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -761,7 +762,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-host-rewrite",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -814,7 +815,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-host-rewrite-with-remote-cwd",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -861,7 +862,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-host-sanitize",
|
||||
{
|
||||
command: "echo ok",
|
||||
@@ -927,7 +928,7 @@ describe("exec resolve_exec_env hook wiring", () => {
|
||||
sessionKey: "agent:main:telegram:chat-1",
|
||||
});
|
||||
|
||||
await definition.execute(
|
||||
await expectDefined(definition, "definition test invariant").execute(
|
||||
"call-command-rewrite",
|
||||
{
|
||||
env: { REQUEST_SAFE: "request" },
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Exercises exec and process behavior through the shared exported tool factory.
|
||||
*/
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { drainFormattedSystemEvents } from "../auto-reply/reply/session-system-events.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
@@ -907,7 +908,9 @@ describe("exec PATH handling", () => {
|
||||
expect(index).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
for (let i = 1; i < prependIndexes.length; i += 1) {
|
||||
expect(prependIndexes[i]).toBeGreaterThan(prependIndexes[i - 1]);
|
||||
expect(prependIndexes[i]).toBeGreaterThan(
|
||||
expectDefined(prependIndexes[i - 1], "prependIndexes[i - 1] test invariant"),
|
||||
);
|
||||
}
|
||||
const baseIndex = entries.indexOf(basePath);
|
||||
expect(baseIndex).toBeGreaterThanOrEqual(0);
|
||||
@@ -972,7 +975,7 @@ describe("applyPathPrepend with case-insensitive PATH key", () => {
|
||||
const existingPath = existing.join(delim);
|
||||
const env: Record<string, string> = { Path: existingPath };
|
||||
applyPathPrepend(env, prepend);
|
||||
const parts = env.Path.split(delim);
|
||||
const parts = expectDefined(env.Path, "env.Path test invariant").split(delim);
|
||||
expect(parts[0]).toBe(prepend[0]);
|
||||
for (const entry of existing) {
|
||||
expect(parts).toContain(entry);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Tests agent bootstrap file discovery, filtering, and injected context modes. */
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearInternalHooks,
|
||||
@@ -298,7 +299,11 @@ describe("resolveBootstrapFilesForRun", () => {
|
||||
["HEARTBEAT.md", "heartbeat"],
|
||||
["BOOTSTRAP.md", "setup"],
|
||||
].map(([fileName, content]) =>
|
||||
fs.writeFile(path.join(workspaceDir, fileName), content, "utf8"),
|
||||
fs.writeFile(
|
||||
path.join(workspaceDir, expectDefined(fileName, "fileName test invariant")),
|
||||
expectDefined(content, "content test invariant"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -323,7 +328,11 @@ describe("resolveBootstrapFilesForRun", () => {
|
||||
["HEARTBEAT.md", "heartbeat"],
|
||||
["BOOTSTRAP.md", "setup"],
|
||||
].map(([fileName, content]) =>
|
||||
fs.writeFile(path.join(workspaceDir, fileName), content, "utf8"),
|
||||
fs.writeFile(
|
||||
path.join(workspaceDir, expectDefined(fileName, "fileName test invariant")),
|
||||
expectDefined(content, "content test invariant"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests BTW side-question execution, session context, auth, and harness routing. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
@@ -2245,7 +2247,10 @@ describe("runBtwSideQuestion", () => {
|
||||
const [message] = contextMessages(streamContext());
|
||||
expectRecordFields(message, { role: "user" });
|
||||
expectTextBlockContains(
|
||||
(message.content as Array<unknown>)[0],
|
||||
expectDefined(
|
||||
(expectDefined(message, "message test invariant").content as Array<unknown>)[0],
|
||||
"(message.content as Array<unknown>)[0] test invariant",
|
||||
),
|
||||
"<in_flight_main_task>\nbuild me a tic-tac-toe game in brainfuck\n</in_flight_main_task>",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests CLI backend config resolution, normalization, and live-test defaults. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { CliBackendConfig } from "../config/types.js";
|
||||
@@ -166,7 +168,7 @@ function normalizeTestClaudeArgs(
|
||||
let hasSettingSources = false;
|
||||
let hasPermissionMode = false;
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
const arg = expectDefined(args[i], "args[i] test invariant");
|
||||
if (arg === "--dangerously-skip-permissions") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests cron before_agent_reply gating at the CLI runner entrypoint. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import type { CliOutput } from "./cli-output.js";
|
||||
@@ -338,7 +340,10 @@ describe("runCliAgent cron before_agent_reply seam", () => {
|
||||
expect(executePreparedCliRunMock).toHaveBeenCalledTimes(1);
|
||||
expect(closeClaudeLiveSessionForContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(closeClaudeLiveSessionForContextMock).toHaveBeenCalledWith(
|
||||
await prepareCliRunContextMock.mock.results[0].value,
|
||||
await expectDefined(
|
||||
prepareCliRunContextMock.mock.results[0],
|
||||
"prepareCliRunContextMock.mock.results[0] test invariant",
|
||||
).value,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared";
|
||||
import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { ImageContent } from "openclaw/plugin-sdk/llm";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createSolidPngBuffer } from "../../test/helpers/image-fixtures.js";
|
||||
@@ -258,9 +259,11 @@ describe("writeCliImages", () => {
|
||||
),
|
||||
]);
|
||||
expect(second.paths).toEqual(first.paths);
|
||||
await expect(fs.readFile(first.paths[0])).resolves.toEqual(Buffer.from(image.data, "base64"));
|
||||
await expect(
|
||||
fs.readFile(expectDefined(first.paths[0], "first.paths[0] test invariant")),
|
||||
).resolves.toEqual(Buffer.from(image.data, "base64"));
|
||||
} finally {
|
||||
await fs.rm(first.paths[0], { force: true });
|
||||
await fs.rm(expectDefined(first.paths[0], "first.paths[0] test invariant"), { force: true });
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -284,7 +287,9 @@ describe("writeCliImages", () => {
|
||||
try {
|
||||
expect(written.paths[0]).toMatch(/\.heic$/);
|
||||
} finally {
|
||||
await fs.rm(written.paths[0], { force: true });
|
||||
await fs.rm(expectDefined(written.paths[0], "written.paths[0] test invariant"), {
|
||||
force: true,
|
||||
});
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -317,9 +322,9 @@ describe("writeCliImages", () => {
|
||||
try {
|
||||
await expect(fs.access(stalePath)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(fs.readFile(freshPath, "utf-8")).resolves.toBe("fresh");
|
||||
await expect(fs.readFile(written.paths[0])).resolves.toEqual(
|
||||
Buffer.from(image.data, "base64"),
|
||||
);
|
||||
await expect(
|
||||
fs.readFile(expectDefined(written.paths[0], "written.paths[0] test invariant")),
|
||||
).resolves.toEqual(Buffer.from(image.data, "base64"));
|
||||
} finally {
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createSolidPngBuffer } from "../../test/helpers/image-fixtures.js";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
@@ -683,8 +684,12 @@ describe("runCliAgent reliability", () => {
|
||||
: [],
|
||||
);
|
||||
expect(imagePaths).toHaveLength(2);
|
||||
expect(fs.readFileSync(imagePaths[0])).toEqual(offloadedImage);
|
||||
expect(fs.readFileSync(imagePaths[1])).toEqual(inlineImage);
|
||||
expect(fs.readFileSync(expectDefined(imagePaths[0], "imagePaths[0] test invariant"))).toEqual(
|
||||
offloadedImage,
|
||||
);
|
||||
expect(fs.readFileSync(expectDefined(imagePaths[1], "imagePaths[1] test invariant"))).toEqual(
|
||||
inlineImage,
|
||||
);
|
||||
expect(argv.includes("resume")).toBe(index === 0);
|
||||
expect(argv.includes("stale-cli-session")).toBe(index === 0);
|
||||
}
|
||||
@@ -3814,7 +3819,9 @@ describe("runCliAgent reliability", () => {
|
||||
expect(JSON.stringify(hookRunner.runAgentEnd.mock.calls)).not.toContain("secret prompt");
|
||||
|
||||
const lines = fs.readFileSync(sessionFile, "utf-8").trim().split("\n");
|
||||
const blockedLine = JSON.parse(lines[lines.length - 1]);
|
||||
const blockedLine = JSON.parse(
|
||||
expectDefined(lines[lines.length - 1], "lines[lines.length - 1] test invariant"),
|
||||
);
|
||||
expect(blockedLine.message.content[0].text).toBe(
|
||||
"Your message could not be sent: The agent cannot read this message. (blocked by policy-plugin)",
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
testing as replyRunTesting,
|
||||
@@ -1063,7 +1064,10 @@ describe("runCliAgent spawn path", () => {
|
||||
const input = (args[0] ?? {}) as { argv?: string[] };
|
||||
const configArg = requireArgAfter(input.argv, "-c");
|
||||
const match = requireRegexMatch(configArg, /^model_instructions_file="(.+)"$/);
|
||||
promptFileText = await fs.readFile(match[1], "utf-8");
|
||||
promptFileText = await fs.readFile(
|
||||
expectDefined(match[1], "match[1] test invariant"),
|
||||
"utf-8",
|
||||
);
|
||||
return createManagedRun({
|
||||
reason: "exit",
|
||||
exitCode: 0,
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { CURRENT_SESSION_VERSION } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { Type } from "typebox";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -3414,7 +3415,10 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
|
||||
native: [],
|
||||
mcp: ["mcp__openclaw__crestodian"],
|
||||
});
|
||||
const mcpConfigPath = args[args.indexOf("--mcp-config") + 1];
|
||||
const mcpConfigPath = expectDefined(
|
||||
args[args.indexOf("--mcp-config") + 1],
|
||||
'args[args.indexOf("--mcp-config") + 1] test invariant',
|
||||
);
|
||||
const raw = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8")) as {
|
||||
mcpServers?: Record<string, { env?: Record<string, string> }>;
|
||||
};
|
||||
|
||||
+203
-120
@@ -1,4 +1,6 @@
|
||||
/** Tests Code Mode tool registration, namespace filtering, and run lifecycle. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { isRecord } from "../../packages/normalization-core/src/record-coerce.js";
|
||||
import { setPluginToolMeta } from "../plugins/tools.js";
|
||||
@@ -304,8 +306,10 @@ describe("Code Mode", () => {
|
||||
it("marks only the internal wait control as hidden from channel progress", () => {
|
||||
const { tools } = createCodeModeHarness();
|
||||
|
||||
expect(tools[0].hideFromChannelProgress).toBeUndefined();
|
||||
expect(tools[1].hideFromChannelProgress).toBe(true);
|
||||
expect(
|
||||
expectDefined(tools[0], "tools[0] test invariant").hideFromChannelProgress,
|
||||
).toBeUndefined();
|
||||
expect(expectDefined(tools[1], "tools[1] test invariant").hideFromChannelProgress).toBe(true);
|
||||
});
|
||||
|
||||
it("tells models to return the final code value", () => {
|
||||
@@ -358,7 +362,7 @@ describe("Code Mode", () => {
|
||||
|
||||
it("uses a flat enum for the exec language schema", () => {
|
||||
const { tools } = createCodeModeHarness();
|
||||
const parameters = tools[0].parameters as {
|
||||
const parameters = expectDefined(tools[0], "tools[0] test invariant").parameters as {
|
||||
properties?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
const language = parameters.properties?.language;
|
||||
@@ -373,7 +377,7 @@ describe("Code Mode", () => {
|
||||
|
||||
it("describes code-mode runtime constraints in the model-visible exec schema", () => {
|
||||
const { tools } = createCodeModeHarness();
|
||||
const execTool = tools[0];
|
||||
const execTool = expectDefined(tools[0], "tools[0] test invariant");
|
||||
const parameters = execTool.parameters as {
|
||||
properties?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
@@ -539,7 +543,7 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
tools[0].execute("code-call-bad-path", {
|
||||
expectDefined(tools[0], "tools[0] test invariant").execute("code-call-bad-path", {
|
||||
code: "return 1;",
|
||||
}),
|
||||
).rejects.toThrow("Invalid code mode namespace path segment: constructor");
|
||||
@@ -556,7 +560,7 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
tools[0].execute("code-call-circular", {
|
||||
expectDefined(tools[0], "tools[0] test invariant").execute("code-call-circular", {
|
||||
code: "return 1;",
|
||||
}),
|
||||
).rejects.toThrow("Circular code mode namespace scope at self");
|
||||
@@ -573,7 +577,7 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
tools[0].execute("code-call-raw-function", {
|
||||
expectDefined(tools[0], "tools[0] test invariant").execute("code-call-raw-function", {
|
||||
code: "return 1;",
|
||||
}),
|
||||
).rejects.toThrow("must be created with createCodeModeNamespaceTool");
|
||||
@@ -600,8 +604,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: 'return { global: typeof Hidden, mapped: "Hidden" in namespaces };',
|
||||
});
|
||||
|
||||
@@ -633,8 +637,8 @@ describe("Code Mode", () => {
|
||||
expect(compacted.tools[0]?.description).not.toContain("Hidden: Hidden helpers.");
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: 'return { global: typeof Hidden, mapped: "Hidden" in namespaces };',
|
||||
});
|
||||
|
||||
@@ -667,8 +671,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const left = await Shared.left.read();
|
||||
const right = await Shared.right.read();
|
||||
@@ -707,8 +711,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
globalThis.__openclawHostRequest("namespace", JSON.stringify(["leaky", ["hidden"], []]));
|
||||
await yield_control("pause");
|
||||
@@ -757,7 +761,7 @@ describe("Code Mode", () => {
|
||||
catalogRef,
|
||||
});
|
||||
const result = resultDetails(
|
||||
await tools[0].execute("code-call-command-alias", {
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-command-alias", {
|
||||
command: "return 7;",
|
||||
}),
|
||||
);
|
||||
@@ -778,7 +782,7 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
tools[0].execute("code-call-divergent-alias", {
|
||||
expectDefined(tools[0], "tools[0] test invariant").execute("code-call-divergent-alias", {
|
||||
code: "return 1;",
|
||||
command: "return 2;",
|
||||
}),
|
||||
@@ -798,8 +802,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const hits = await tools.search("ticket", { limit: 1 });
|
||||
const described = await tools.describe(hits[0].id);
|
||||
@@ -831,8 +835,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
try {
|
||||
await tools.call("file_write", {
|
||||
@@ -865,8 +869,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
try {
|
||||
await tools.call("missing_tool", {});
|
||||
@@ -913,8 +917,8 @@ describe("Code Mode", () => {
|
||||
expect(compacted.tools[0]?.description).toContain("visible servers: github");
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const rootApi = await MCP.$api();
|
||||
const api = await MCP.github.$api("createIssue", { schema: true });
|
||||
@@ -1046,8 +1050,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const files = await API.list("mcp");
|
||||
const api = await API.read("mcp/github.d.ts");
|
||||
@@ -1120,8 +1124,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const api = await MCP.docs.$api();
|
||||
const resource = await MCP.docs.resources.read({ uri: "memo://one" });
|
||||
@@ -1168,8 +1172,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: 'return (await MCP.constructor2.prototype2({ value: "safe" })).details;',
|
||||
});
|
||||
|
||||
@@ -1228,8 +1232,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const direct = await Tickets.issues.list({ state: "open" });
|
||||
const mapped = await namespaces.Tickets.issues.list({ state: "closed" });
|
||||
@@ -1288,8 +1292,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: 'return await Owned.list({ value: "safe" });',
|
||||
});
|
||||
|
||||
@@ -1340,8 +1344,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: "return await Context.read();",
|
||||
});
|
||||
|
||||
@@ -1379,8 +1383,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
try {
|
||||
await Broken.fail();
|
||||
@@ -1407,15 +1411,18 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-yield", {
|
||||
restartSafe: true,
|
||||
code: `
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-yield",
|
||||
{
|
||||
restartSafe: true,
|
||||
code: `
|
||||
text("before");
|
||||
await yield_control("pause");
|
||||
text("after");
|
||||
return "done";
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(first.status).toBe("waiting");
|
||||
@@ -1425,7 +1432,12 @@ describe("Code Mode", () => {
|
||||
|
||||
const runId = first.runId;
|
||||
expect(typeof runId).toBe("string");
|
||||
const resumed = resultDetails(await codeModeTools[1].execute("code-wait-yield", { runId }));
|
||||
const resumed = resultDetails(
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-yield",
|
||||
{ runId },
|
||||
),
|
||||
);
|
||||
|
||||
expect(resumed.status).toBe("completed");
|
||||
expect(resumed.value).toBe("done");
|
||||
@@ -1448,27 +1460,36 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-replay-safety", {
|
||||
restartSafe: true,
|
||||
code: `
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-replay-safety",
|
||||
{
|
||||
restartSafe: true,
|
||||
code: `
|
||||
const matches = await tools.search(${JSON.stringify(targetTool.name)});
|
||||
return await tools.call(matches[0].id, {});
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
expect(first.replaySafe).toBe(true);
|
||||
|
||||
const second = resultDetails(
|
||||
await codeModeTools[1].execute("code-wait-replay-safety", { runId: first.runId }),
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-replay-safety",
|
||||
{ runId: first.runId },
|
||||
),
|
||||
);
|
||||
expect(second.status).toBe("waiting");
|
||||
expect(second.replaySafe).toBe(true);
|
||||
|
||||
const completed = resultDetails(
|
||||
await codeModeTools[1].execute("code-wait-replay-safety-complete", {
|
||||
runId: second.runId,
|
||||
}),
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-replay-safety-complete",
|
||||
{
|
||||
runId: second.runId,
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(completed.status).toBe("completed");
|
||||
});
|
||||
@@ -1491,8 +1512,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const completed = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
restartSafe: true,
|
||||
code: `
|
||||
const matches = await tools.search("fake_plugin_read");
|
||||
@@ -1533,8 +1554,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const completed = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
restartSafe: true,
|
||||
code: 'return await MCP.github.readFile({ path: "README.md" });',
|
||||
});
|
||||
@@ -1558,19 +1579,25 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-unsafe-restart", {
|
||||
restartSafe: true,
|
||||
code: `
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-unsafe-restart",
|
||||
{
|
||||
restartSafe: true,
|
||||
code: `
|
||||
const matches = await tools.search("fake_write");
|
||||
return await tools.call(matches[0].id, {});
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
expect(first.replaySafe).toBe(true);
|
||||
|
||||
const failed = resultDetails(
|
||||
await codeModeTools[1].execute("code-wait-unsafe-restart", { runId: first.runId }),
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-unsafe-restart",
|
||||
{ runId: first.runId },
|
||||
),
|
||||
);
|
||||
expect(failed.status).toBe("failed");
|
||||
expect(failed.error).toContain("cannot call side-effecting tools");
|
||||
@@ -1596,19 +1623,25 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-forced-restart", {
|
||||
restartSafe: false,
|
||||
code: `
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-forced-restart",
|
||||
{
|
||||
restartSafe: false,
|
||||
code: `
|
||||
const matches = await tools.search("fake_forced_write");
|
||||
return await tools.call(matches[0].id, {});
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
expect(first.replaySafe).toBe(true);
|
||||
|
||||
const failed = resultDetails(
|
||||
await codeModeTools[1].execute("code-wait-forced-restart", { runId: first.runId }),
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-forced-restart",
|
||||
{ runId: first.runId },
|
||||
),
|
||||
);
|
||||
expect(failed.status).toBe("failed");
|
||||
expect(failed.error).toContain("cannot call side-effecting tools");
|
||||
@@ -1629,9 +1662,12 @@ describe("Code Mode", () => {
|
||||
let details: Record<string, unknown>;
|
||||
try {
|
||||
details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-yield-overflow", {
|
||||
code: 'await yield_control("pause"); return "done";',
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-yield-overflow",
|
||||
{
|
||||
code: 'await yield_control("pause"); return "done";',
|
||||
},
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
nowSpy.mockRestore();
|
||||
@@ -1649,7 +1685,10 @@ describe("Code Mode", () => {
|
||||
} as never);
|
||||
|
||||
await expect(
|
||||
codeModeTools[1].execute("code-wait-invalid-expiry", { runId: "invalid-expiry-run" }),
|
||||
expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-invalid-expiry",
|
||||
{ runId: "invalid-expiry-run" },
|
||||
),
|
||||
).rejects.toThrow("code mode run is unavailable or expired");
|
||||
expect(testing.activeRuns.has("invalid-expiry-run")).toBe(false);
|
||||
});
|
||||
@@ -1666,19 +1705,25 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-wrong-session", {
|
||||
code: 'await yield_control("pause"); return "done";',
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-wrong-session",
|
||||
{
|
||||
code: 'await yield_control("pause"); return "done";',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
const otherWaitTool = createCodeModeTools({
|
||||
config,
|
||||
runtimeConfig: config,
|
||||
sessionId: "other-session",
|
||||
sessionKey: "agent:other:main",
|
||||
runId: "run-code-mode",
|
||||
catalogRef,
|
||||
})[1];
|
||||
const otherWaitTool = expectDefined(
|
||||
createCodeModeTools({
|
||||
config,
|
||||
runtimeConfig: config,
|
||||
sessionId: "other-session",
|
||||
sessionKey: "agent:other:main",
|
||||
runId: "run-code-mode",
|
||||
catalogRef,
|
||||
})[1],
|
||||
'createCodeModeTools({ config, runtimeConfig: config, sessionId: "othe... test invariant',
|
||||
);
|
||||
|
||||
await expect(
|
||||
otherWaitTool.execute("code-wait-wrong-session", { runId: first.runId }),
|
||||
@@ -1721,17 +1766,26 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-concurrent-wait", {
|
||||
code: "await tools.fake_slow({}); return 'done';",
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-concurrent-wait",
|
||||
{
|
||||
code: "await tools.fake_slow({}); return 'done';",
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
|
||||
const firstWait = codeModeTools[1].execute("code-wait-concurrent-a", {
|
||||
runId: first.runId,
|
||||
});
|
||||
const firstWait = expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-concurrent-a",
|
||||
{
|
||||
runId: first.runId,
|
||||
},
|
||||
);
|
||||
await expect(
|
||||
codeModeTools[1].execute("code-wait-concurrent-b", { runId: first.runId }),
|
||||
expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-concurrent-b",
|
||||
{ runId: first.runId },
|
||||
),
|
||||
).rejects.toThrow("already being resumed");
|
||||
const stillWaiting = resultDetails(await firstWait);
|
||||
|
||||
@@ -1776,15 +1830,18 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const first = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-timeout", {
|
||||
code: `
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-timeout",
|
||||
{
|
||||
code: `
|
||||
const fast = tools.fake_fast({});
|
||||
const slow = tools.fake_slow({});
|
||||
await fast;
|
||||
await slow;
|
||||
return "done";
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(first.status).toBe("waiting");
|
||||
expect(first.pendingToolCalls).toHaveLength(2);
|
||||
@@ -1798,7 +1855,12 @@ describe("Code Mode", () => {
|
||||
expect(activeRun).toBeDefined();
|
||||
activeRun!.config.timeoutMs = 100;
|
||||
|
||||
const second = resultDetails(await codeModeTools[1].execute("code-wait-timeout", { runId }));
|
||||
const second = resultDetails(
|
||||
await expectDefined(codeModeTools[1], "codeModeTools[1] test invariant").execute(
|
||||
"code-wait-timeout",
|
||||
{ runId },
|
||||
),
|
||||
);
|
||||
|
||||
expect(second.status).toBe("waiting");
|
||||
expect(second.pendingToolCalls).toEqual([expect.objectContaining({ method: "call" })]);
|
||||
@@ -1816,8 +1878,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: "return 42;",
|
||||
});
|
||||
|
||||
@@ -1838,8 +1900,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: `
|
||||
const important = 41;
|
||||
const message = "import docs later";
|
||||
@@ -1864,9 +1926,12 @@ describe("Code Mode", () => {
|
||||
|
||||
const beforeRunCount = testing.activeRuns.size;
|
||||
const details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-empty-wait", {
|
||||
code: "await new Promise(() => undefined); return 'never';",
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-empty-wait",
|
||||
{
|
||||
code: "await new Promise(() => undefined); return 'never';",
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -1886,7 +1951,10 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-syntax", { code: "const x = ;" }),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-syntax",
|
||||
{ code: "const x = ;" },
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -1911,7 +1979,10 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-runtime", { code: "return missingFn();" }),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-runtime",
|
||||
{ code: "return missingFn();" },
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -1933,9 +2004,12 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-host-error", {
|
||||
code: 'return globalThis.__openclawHostRequest("unsupported", "[]");',
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-host-error",
|
||||
{
|
||||
code: 'return globalThis.__openclawHostRequest("unsupported", "[]");',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(details).toMatchObject({
|
||||
@@ -1980,8 +2054,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
code: 'const hits = await tools.search("ticket"); return hits.length;',
|
||||
});
|
||||
|
||||
@@ -2012,8 +2086,8 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = await runUntilCompleted({
|
||||
execTool: codeModeTools[0],
|
||||
waitTool: codeModeTools[1],
|
||||
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
|
||||
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
|
||||
language: "typescript",
|
||||
code: `
|
||||
const value: number = 40 + 2;
|
||||
@@ -2042,9 +2116,12 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await codeModeTools[0].execute("code-call-import", {
|
||||
code,
|
||||
}),
|
||||
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
|
||||
"code-call-import",
|
||||
{
|
||||
code,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -2080,7 +2157,7 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await tools[0].execute("code-call-large", {
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-large", {
|
||||
code: "return 'x'.repeat(2048);",
|
||||
}),
|
||||
);
|
||||
@@ -2120,7 +2197,7 @@ describe("Code Mode", () => {
|
||||
|
||||
const beforeRunCount = testing.activeRuns.size;
|
||||
const details = resultDetails(
|
||||
await tools[0].execute("code-call-large-suspend", {
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-large-suspend", {
|
||||
code: "text('x'.repeat(2048)); await yield_control('pause'); return 1;",
|
||||
}),
|
||||
);
|
||||
@@ -2172,9 +2249,12 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await tools[0].execute("code-call-large-namespace", {
|
||||
code: 'text("x".repeat(2048)); await Tickets.list({ state: "open" }); return 1;',
|
||||
}),
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute(
|
||||
"code-call-large-namespace",
|
||||
{
|
||||
code: 'text("x".repeat(2048)); await Tickets.list({ state: "open" }); return 1;',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -2195,9 +2275,12 @@ describe("Code Mode", () => {
|
||||
});
|
||||
|
||||
const details = resultDetails(
|
||||
await tools[0].execute("code-call-output-before-error", {
|
||||
code: 'text("before"); throw new Error("boom");',
|
||||
}),
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute(
|
||||
"code-call-output-before-error",
|
||||
{
|
||||
code: 'text("before"); throw new Error("boom");',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(details.status).toBe("failed");
|
||||
@@ -2257,7 +2340,7 @@ describe("Code Mode", () => {
|
||||
|
||||
const heartbeat = Promise.resolve("main-event-loop-alive");
|
||||
const details = resultDetails(
|
||||
await tools[0].execute("code-call-loop", {
|
||||
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-loop", {
|
||||
code: "while (true) {}",
|
||||
}),
|
||||
);
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
// Covers session-message sanitization for empty blocks, tool ids, and
|
||||
// thought-signature replay rules.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { AssistantMessage, ToolResultMessage, UserMessage } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
@@ -67,13 +69,13 @@ function makeOpenAiResponsesAssistantMessage(
|
||||
function expectToolCallAndResultIds(out: AgentMessage[], expectedId: string) {
|
||||
// Tool call and result ids must stay in lockstep or replayed transcripts break
|
||||
// provider tool-result matching.
|
||||
const assistant = out[0];
|
||||
const assistant = expectDefined(out[0], "out[0] test invariant");
|
||||
expect(assistant.role).toBe("assistant");
|
||||
const assistantContent = assistant.role === "assistant" ? assistant.content : [];
|
||||
const toolCall = assistantContent.find((block) => block.type === "toolCall");
|
||||
expect(toolCall?.id).toBe(expectedId);
|
||||
|
||||
const toolResult = out[1];
|
||||
const toolResult = expectDefined(out[1], "out[1] test invariant");
|
||||
expect(toolResult.role).toBe("toolResult");
|
||||
if (toolResult.role === "toolResult") {
|
||||
expect(toolResult.toolCallId).toBe(expectedId);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Regression coverage for user-facing text sanitization.
|
||||
* Includes reasoning/tool-call cleanup and internal event prompt formatting.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
downgradeOpenAIFunctionCallReasoningPairs,
|
||||
@@ -689,8 +691,8 @@ describe("stripThoughtSignatures", () => {
|
||||
thinking: "test",
|
||||
thought_signature: "AQID",
|
||||
});
|
||||
expect("thought_signature" in result[0]).toBe(false);
|
||||
expect("thought_signature" in result[1]).toBe(true);
|
||||
expect("thought_signature" in expectDefined(result[0], "result[0] test invariant")).toBe(false);
|
||||
expect("thought_signature" in expectDefined(result[1], "result[1] test invariant")).toBe(true);
|
||||
});
|
||||
it("preserves blocks without thought_signature", () => {
|
||||
const input = [
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Covers provider-specific transcript turn validation and repair.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
@@ -284,7 +286,7 @@ describe("validateAnthropicTurns", () => {
|
||||
const result = validateAnthropicTurns(msgs) as Extract<AgentMessage, { role: "user" }>[];
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
const merged = result[0];
|
||||
const merged = expectDefined(result[0], "merged user message");
|
||||
expect(merged.timestamp).toBe(2000);
|
||||
expect((merged as { attachments?: unknown[] }).attachments).toEqual([
|
||||
{ type: "image", url: "new.png" },
|
||||
@@ -315,7 +317,7 @@ describe("validateAnthropicTurns", () => {
|
||||
]);
|
||||
|
||||
const [merged] = validateAnthropicTurns(msgs) as Extract<AgentMessage, { role: "user" }>[];
|
||||
expect(merged.content).toEqual([
|
||||
expect(expectDefined(merged, "merged test invariant").content).toEqual([
|
||||
{ type: "text", text: "first" },
|
||||
{ type: "image", url: "img1" },
|
||||
{ type: "image", url: "img2" },
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Covers Moonshot-specific extra-params thinking payload behavior.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
createMoonshotThinkingWrapper,
|
||||
@@ -228,7 +230,7 @@ describe("applyExtraParamsToAgent Moonshot", () => {
|
||||
expect(payload).not.toHaveProperty("presence_penalty");
|
||||
expect(payload).not.toHaveProperty("frequency_penalty");
|
||||
const messages = payload.messages as Array<Record<string, unknown>>;
|
||||
expect(messages[0].reasoning_content).toBe("");
|
||||
expect(expectDefined(messages[0], "messages[0] test invariant").reasoning_content).toBe("");
|
||||
});
|
||||
|
||||
it("repairs only missing assistant tool-call reasoning_content when thinking is enabled", () => {
|
||||
@@ -261,8 +263,10 @@ describe("applyExtraParamsToAgent Moonshot", () => {
|
||||
|
||||
expect(payload.thinking).toEqual({ type: "enabled" });
|
||||
const messages = payload.messages as Array<Record<string, unknown>>;
|
||||
expect(messages[1].reasoning_content).toBe("");
|
||||
expect(messages[2].reasoning_content).toBe("native reasoning");
|
||||
expect(expectDefined(messages[1], "messages[1] test invariant").reasoning_content).toBe("");
|
||||
expect(expectDefined(messages[2], "messages[2] test invariant").reasoning_content).toBe(
|
||||
"native reasoning",
|
||||
);
|
||||
expect(messages[3]).not.toHaveProperty("reasoning_content");
|
||||
});
|
||||
|
||||
@@ -284,6 +288,8 @@ describe("applyExtraParamsToAgent Moonshot", () => {
|
||||
});
|
||||
|
||||
const messages = payload.messages as Array<Record<string, unknown>>;
|
||||
expect(messages[0].reasoning_content).toBeUndefined();
|
||||
expect(
|
||||
expectDefined(messages[0], "messages[0] test invariant").reasoning_content,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Covers delayed flushing of pending tool results after agent idle.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -99,7 +101,7 @@ describe("flushPendingToolResultsAfterIdle", () => {
|
||||
const entries = getMessages(sm);
|
||||
|
||||
expect(entries.length).toBe(2);
|
||||
expect(entries[1].role).toBe("toolResult");
|
||||
expect(expectDefined(entries[1], "entries[1] test invariant").role).toBe("toolResult");
|
||||
expect((entries[1] as { isError?: boolean }).isError).toBe(true);
|
||||
expect((entries[1] as { content?: Array<{ text?: string }> }).content?.[0]?.text).toContain(
|
||||
"missing tool result",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Covers limiting persisted history by recent user turns.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { limitHistoryTurns } from "./embedded-agent-runner/history.js";
|
||||
@@ -97,15 +99,15 @@ describe("limitHistoryTurns", () => {
|
||||
const messages = makeMessages(["user", "assistant", "user", "assistant", "user", "assistant"]);
|
||||
const limited = limitHistoryTurns(messages, 2);
|
||||
expect(limited.length).toBe(4);
|
||||
expect(firstText(limited[0])).toBe("message 2");
|
||||
expect(firstText(expectDefined(limited[0], "limited[0] test invariant"))).toBe("message 2");
|
||||
});
|
||||
|
||||
it("handles single user turn limit", () => {
|
||||
const messages = makeMessages(["user", "assistant", "user", "assistant", "user", "assistant"]);
|
||||
const limited = limitHistoryTurns(messages, 1);
|
||||
expect(limited.length).toBe(2);
|
||||
expect(firstText(limited[0])).toBe("message 4");
|
||||
expect(firstText(limited[1])).toBe("message 5");
|
||||
expect(firstText(expectDefined(limited[0], "limited[0] test invariant"))).toBe("message 4");
|
||||
expect(firstText(expectDefined(limited[1], "limited[1] test invariant"))).toBe("message 5");
|
||||
});
|
||||
|
||||
it("handles messages with multiple assistant responses per user turn", () => {
|
||||
@@ -114,8 +116,8 @@ describe("limitHistoryTurns", () => {
|
||||
const messages = makeMessages(["user", "assistant", "assistant", "user", "assistant"]);
|
||||
const limited = limitHistoryTurns(messages, 1);
|
||||
expect(limited.length).toBe(2);
|
||||
expect(limited[0].role).toBe("user");
|
||||
expect(limited[1].role).toBe("assistant");
|
||||
expect(expectDefined(limited[0], "limited[0] test invariant").role).toBe("user");
|
||||
expect(expectDefined(limited[1], "limited[1] test invariant").role).toBe("assistant");
|
||||
});
|
||||
|
||||
it("preserves leading compactionSummary when limiting", () => {
|
||||
@@ -133,8 +135,8 @@ describe("limitHistoryTurns", () => {
|
||||
const limited = limitHistoryTurns(messages, 1);
|
||||
// compactionSummary is preserved, last 1 user turn + assistant kept
|
||||
expect(limited.length).toBe(3);
|
||||
expect(limited[0].role).toBe("compactionSummary");
|
||||
expect(firstText(limited[1])).toBe("message 2");
|
||||
expect(expectDefined(limited[0], "limited[0] test invariant").role).toBe("compactionSummary");
|
||||
expect(firstText(expectDefined(limited[1], "limited[1] test invariant"))).toBe("message 2");
|
||||
});
|
||||
|
||||
it("preserves leading branchSummary when limiting", () => {
|
||||
@@ -147,7 +149,7 @@ describe("limitHistoryTurns", () => {
|
||||
const messages = [branchSummary, ...makeMessages(["user", "assistant", "user", "assistant"])];
|
||||
const limited = limitHistoryTurns(messages, 1);
|
||||
expect(limited.length).toBe(3);
|
||||
expect(limited[0].role).toBe("branchSummary");
|
||||
expect(expectDefined(limited[0], "limited[0] test invariant").role).toBe("branchSummary");
|
||||
});
|
||||
|
||||
it("returns all when only non-conversation messages exist", () => {
|
||||
@@ -159,7 +161,7 @@ describe("limitHistoryTurns", () => {
|
||||
} as AgentMessage;
|
||||
const limited = limitHistoryTurns([compactionSummary], 2);
|
||||
expect(limited).toHaveLength(1);
|
||||
expect(limited[0].role).toBe("compactionSummary");
|
||||
expect(expectDefined(limited[0], "limited[0] test invariant").role).toBe("compactionSummary");
|
||||
});
|
||||
|
||||
it("preserves message content integrity", () => {
|
||||
@@ -171,7 +173,7 @@ describe("limitHistoryTurns", () => {
|
||||
assistantTextMessage("response"),
|
||||
];
|
||||
const limited = limitHistoryTurns(messages, 1);
|
||||
expect(firstText(limited[0])).toBe("second");
|
||||
expect(firstText(limited[1])).toBe("response");
|
||||
expect(firstText(expectDefined(limited[0], "limited[0] test invariant"))).toBe("second");
|
||||
expect(firstText(expectDefined(limited[1], "limited[1] test invariant"))).toBe("response");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Hook integration coverage for direct and queued embedded compaction.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
|
||||
import { createReplyOperation } from "../../auto-reply/reply/reply-run-registry.js";
|
||||
@@ -791,7 +793,10 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
"compaction system prompt",
|
||||
);
|
||||
expect(createdSession.session.setActiveToolsByName.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
createdSession.session.setBaseSystemPrompt.mock.invocationCallOrder[0],
|
||||
expectDefined(
|
||||
createdSession.session.setBaseSystemPrompt.mock.invocationCallOrder[0],
|
||||
"createdSession.session.setBaseSystemPrompt.mock.invocationCallOrder[0] test invariant",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2066,7 +2071,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
] as AgentMessage[];
|
||||
expect(
|
||||
compactTesting.hasRealConversationContent(
|
||||
heartbeatToolResultWindow[1],
|
||||
expectDefined(heartbeatToolResultWindow[1], "heartbeatToolResultWindow[1] test invariant"),
|
||||
heartbeatToolResultWindow,
|
||||
1,
|
||||
),
|
||||
@@ -2084,7 +2089,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
] as AgentMessage[];
|
||||
expect(
|
||||
compactTesting.hasRealConversationContent(
|
||||
realAskToolResultWindow[2],
|
||||
expectDefined(realAskToolResultWindow[2], "realAskToolResultWindow[2] test invariant"),
|
||||
realAskToolResultWindow,
|
||||
2,
|
||||
),
|
||||
@@ -2111,8 +2116,20 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
},
|
||||
] as AgentMessage[];
|
||||
|
||||
expect(compactTesting.hasRealConversationContent(messages[0], messages, 0)).toBe(true);
|
||||
expect(compactTesting.hasRealConversationContent(messages[2], messages, 2)).toBe(true);
|
||||
expect(
|
||||
compactTesting.hasRealConversationContent(
|
||||
expectDefined(messages[0], "messages[0] test invariant"),
|
||||
messages,
|
||||
0,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compactTesting.hasRealConversationContent(
|
||||
expectDefined(messages[2], "messages[2] test invariant"),
|
||||
messages,
|
||||
2,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("registers the Ollama api provider before compaction", () => {
|
||||
@@ -3468,7 +3485,10 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => {
|
||||
{ nativeCompactionRequest: "after_context_engine" },
|
||||
);
|
||||
expect(contextEngineCompactMock.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
maybeCompactAgentHarnessSessionMock.mock.invocationCallOrder[0],
|
||||
expectDefined(
|
||||
maybeCompactAgentHarnessSessionMock.mock.invocationCallOrder[0],
|
||||
"maybeCompactAgentHarnessSessionMock.mock.invocationCallOrder[0] test invariant",
|
||||
),
|
||||
);
|
||||
const details = result.result?.details as
|
||||
| { codexNativeCompaction?: Record<string, unknown> }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Coverage for deferred context-engine maintenance and transcript rewrite hooks.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ContextEngineRuntimeContext } from "../../context-engine/types.js";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../../infra/system-events.js";
|
||||
@@ -686,9 +688,14 @@ describe("runContextEngineMaintenance", () => {
|
||||
});
|
||||
|
||||
await waitForAssertion(() =>
|
||||
expect(getTaskById(queuedTasks[0].taskId)?.status).toBe("succeeded"),
|
||||
expect(
|
||||
getTaskById(expectDefined(queuedTasks[0], "queuedTasks[0] test invariant").taskId)
|
||||
?.status,
|
||||
).toBe("succeeded"),
|
||||
);
|
||||
const completedTask = getTaskById(
|
||||
expectDefined(queuedTasks[0], "queuedTasks[0] test invariant").taskId,
|
||||
);
|
||||
const completedTask = getTaskById(queuedTasks[0].taskId);
|
||||
const completedTaskRecord = requireRecord(completedTask, "completed task");
|
||||
expect(completedTaskRecord.status).toBe("succeeded");
|
||||
expect(String(completedTaskRecord.progressSummary)).toContain(
|
||||
@@ -853,7 +860,10 @@ describe("runContextEngineMaintenance", () => {
|
||||
});
|
||||
expect(deferredPromises).toHaveLength(2);
|
||||
let secondDeferredSettled = false;
|
||||
const secondDeferred = deferredPromises[1].then(() => {
|
||||
const secondDeferred = expectDefined(
|
||||
deferredPromises[1],
|
||||
"deferredPromises[1] test invariant",
|
||||
).then(() => {
|
||||
secondDeferredSettled = true;
|
||||
});
|
||||
|
||||
@@ -1511,9 +1521,14 @@ describe("runContextEngineMaintenance", () => {
|
||||
);
|
||||
expect(tasks).toHaveLength(1);
|
||||
await waitForAssertion(() =>
|
||||
expect(getTaskById(tasks[0].taskId)?.status).toBe("succeeded"),
|
||||
expect(
|
||||
getTaskById(expectDefined(tasks[0], "tasks[0] test invariant").taskId)?.status,
|
||||
).toBe("succeeded"),
|
||||
);
|
||||
const task = requireRecord(
|
||||
getTaskById(expectDefined(tasks[0], "tasks[0] test invariant").taskId),
|
||||
"maintenance task",
|
||||
);
|
||||
const task = requireRecord(getTaskById(tasks[0].taskId), "maintenance task");
|
||||
expectRecordFields(task, {
|
||||
status: "succeeded",
|
||||
notifyPolicy: "silent",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Coverage for OpenRouter Anthropic cache_control payload rewriting.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createOpenRouterSystemCacheWrapper } from "../../llm/providers/stream-wrappers/proxy.js";
|
||||
@@ -44,10 +46,14 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
|
||||
|
||||
expect(payload.messages[0].content).toEqual([
|
||||
expect(
|
||||
expectDefined(payload.messages[0], "payload.messages[0] test invariant").content,
|
||||
).toEqual([
|
||||
{ type: "text", text: "You are a helpful assistant.", cache_control: { type: "ephemeral" } },
|
||||
]);
|
||||
expect(payload.messages[1].content).toBe("Hello");
|
||||
expect(expectDefined(payload.messages[1], "payload.messages[1] test invariant").content).toBe(
|
||||
"Hello",
|
||||
);
|
||||
});
|
||||
|
||||
it("adds cache_control to last content block when system message is already array", () => {
|
||||
@@ -65,7 +71,8 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
|
||||
|
||||
const content = payload.messages[0].content as Array<Record<string, unknown>>;
|
||||
const content = expectDefined(payload.messages[0], "payload.messages[0] test invariant")
|
||||
.content as Array<Record<string, unknown>>;
|
||||
expect(content[0]).toEqual({ type: "text", text: "Part 1" });
|
||||
expect(content[1]).toEqual({
|
||||
type: "text",
|
||||
@@ -84,7 +91,9 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6", { cacheRetention: "long" });
|
||||
|
||||
expect(payload.messages[0].content).toEqual([
|
||||
expect(
|
||||
expectDefined(payload.messages[0], "payload.messages[0] test invariant").content,
|
||||
).toEqual([
|
||||
{
|
||||
type: "text",
|
||||
text: "You are a helpful assistant.",
|
||||
@@ -115,10 +124,12 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6", { cacheRetention: "none" });
|
||||
|
||||
expect(payload.messages[0].content).toBe("You are a helpful assistant.");
|
||||
expect(payload.messages[1].content).toEqual([
|
||||
{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
|
||||
]);
|
||||
expect(expectDefined(payload.messages[0], "payload.messages[0] test invariant").content).toBe(
|
||||
"You are a helpful assistant.",
|
||||
);
|
||||
expect(
|
||||
expectDefined(payload.messages[1], "payload.messages[1] test invariant").content,
|
||||
).toEqual([{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" }]);
|
||||
});
|
||||
|
||||
it("does not inject cache_control for OpenRouter non-Anthropic models", () => {
|
||||
@@ -128,7 +139,9 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "google/gemini-3-pro");
|
||||
|
||||
expect(payload.messages[0].content).toBe("You are a helpful assistant.");
|
||||
expect(expectDefined(payload.messages[0], "payload.messages[0] test invariant").content).toBe(
|
||||
"You are a helpful assistant.",
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves payload unchanged when no system message exists", () => {
|
||||
@@ -138,7 +151,9 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
|
||||
|
||||
expect(payload.messages[0].content).toBe("Hello");
|
||||
expect(expectDefined(payload.messages[0], "payload.messages[0] test invariant").content).toBe(
|
||||
"Hello",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not inject cache_control into thinking blocks", () => {
|
||||
@@ -156,7 +171,9 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
|
||||
|
||||
expect(payload.messages[0].content).toEqual([
|
||||
expect(
|
||||
expectDefined(payload.messages[0], "payload.messages[0] test invariant").content,
|
||||
).toEqual([
|
||||
{ type: "text", text: "Part 1" },
|
||||
{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
|
||||
]);
|
||||
@@ -182,7 +199,9 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
|
||||
|
||||
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
|
||||
|
||||
expect(payload.messages[0].content).toEqual([
|
||||
expect(
|
||||
expectDefined(payload.messages[0], "payload.messages[0] test invariant").content,
|
||||
).toEqual([
|
||||
{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
|
||||
{ type: "text", text: "visible" },
|
||||
]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Coverage for Google prompt-cache creation, reuse, and request rewriting.
|
||||
import crypto from "node:crypto";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { Model } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
@@ -203,7 +204,11 @@ describe("google prompt cache", () => {
|
||||
);
|
||||
|
||||
const headers = fetchInit(fetchMock).headers as Record<string, string>;
|
||||
expect(resolveSecretSentinel(headers.Authorization)).toBe("Bearer google-oauth-token");
|
||||
expect(
|
||||
resolveSecretSentinel(
|
||||
expectDefined(headers.Authorization, "headers.Authorization test invariant"),
|
||||
),
|
||||
).toBe("Bearer google-oauth-token");
|
||||
expect(headers["x-goog-api-key"]).toBeUndefined();
|
||||
expect(headers["Content-Type"]).toBe("application/json");
|
||||
});
|
||||
@@ -236,7 +241,11 @@ describe("google prompt cache", () => {
|
||||
|
||||
const headers = fetchInit(fetchMock).headers as Record<string, string>;
|
||||
expect(headers.Authorization).toBe("Bearer google-kill-switch-token");
|
||||
expect(isSecretValueRegisteredForRedaction(headers.Authorization)).toBe(true);
|
||||
expect(
|
||||
isSecretValueRegisteredForRedaction(
|
||||
expectDefined(headers.Authorization, "headers.Authorization test invariant"),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
vi.unstubAllEnvs();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Coverage for inline provider model normalization and inheritance.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildInlineProviderModels, resolveProviderModelInput } from "./model.inline-provider.js";
|
||||
import { makeModel } from "./model.test-harness.js";
|
||||
@@ -83,10 +85,12 @@ describe("buildInlineProviderModels", () => {
|
||||
const result = buildInlineProviderModels(providers);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].provider).toBe("google");
|
||||
expect(result[0].baseUrl).toBe("https://us-central1-aiplatform.googleapis.com/v1");
|
||||
expect(result[0].api).toBe("google-vertex");
|
||||
expect(result[0].id).toBe("gemini-2.5-pro");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").provider).toBe("google");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").baseUrl).toBe(
|
||||
"https://us-central1-aiplatform.googleapis.com/v1",
|
||||
);
|
||||
expect(expectDefined(result[0], "result[0] test invariant").api).toBe("google-vertex");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").id).toBe("gemini-2.5-pro");
|
||||
});
|
||||
|
||||
it("model-level api takes precedence over provider-level api", () => {
|
||||
@@ -124,10 +128,12 @@ describe("buildInlineProviderModels", () => {
|
||||
const result = buildInlineProviderModels(providers);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].provider).toBe("custom");
|
||||
expect(result[0].baseUrl).toBe("http://localhost:10000");
|
||||
expect(result[0].api).toBe("anthropic-messages");
|
||||
expect(result[0].name).toBe("claude-opus-4.5");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").provider).toBe("custom");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").baseUrl).toBe(
|
||||
"http://localhost:10000",
|
||||
);
|
||||
expect(expectDefined(result[0], "result[0] test invariant").api).toBe("anthropic-messages");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").name).toBe("claude-opus-4.5");
|
||||
});
|
||||
|
||||
it("normalizes bare Google API hosts for custom Google Generative AI providers", () => {
|
||||
@@ -144,9 +150,11 @@ describe("buildInlineProviderModels", () => {
|
||||
const result = buildInlineProviderModels(providers);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].provider).toBe("google-paid");
|
||||
expect(result[0].api).toBe("google-generative-ai");
|
||||
expect(result[0].baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").provider).toBe("google-paid");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").api).toBe("google-generative-ai");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").baseUrl).toBe(
|
||||
"https://generativelanguage.googleapis.com/v1beta",
|
||||
);
|
||||
});
|
||||
|
||||
it("merges provider-level headers into inline models", () => {
|
||||
@@ -187,21 +195,19 @@ describe("buildInlineProviderModels", () => {
|
||||
};
|
||||
|
||||
const result = buildInlineProviderModels(providers);
|
||||
const [
|
||||
{
|
||||
id,
|
||||
name,
|
||||
reasoning,
|
||||
input,
|
||||
cost,
|
||||
contextWindow,
|
||||
maxTokens,
|
||||
provider,
|
||||
baseUrl,
|
||||
api,
|
||||
headers,
|
||||
},
|
||||
] = result;
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
reasoning,
|
||||
input,
|
||||
cost,
|
||||
contextWindow,
|
||||
maxTokens,
|
||||
provider,
|
||||
baseUrl,
|
||||
api,
|
||||
headers,
|
||||
} = expectDefined(result[0], "inline proxy model");
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect({
|
||||
@@ -247,9 +253,11 @@ describe("buildInlineProviderModels", () => {
|
||||
} as unknown as Parameters<typeof buildInlineProviderModels>[0]);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].provider).toBe("proxy");
|
||||
expect(result[0].api).toBe("openai-completions");
|
||||
expect(result[0].baseUrl).toBe("https://proxy.example.com/v1");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").provider).toBe("proxy");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").api).toBe("openai-completions");
|
||||
expect(expectDefined(result[0], "result[0] test invariant").baseUrl).toBe(
|
||||
"https://proxy.example.com/v1",
|
||||
);
|
||||
});
|
||||
|
||||
it("omits headers when neither provider nor model specifies them", () => {
|
||||
@@ -263,7 +271,7 @@ describe("buildInlineProviderModels", () => {
|
||||
const result = buildInlineProviderModels(providers);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].headers).toBeUndefined();
|
||||
expect(expectDefined(result[0], "result[0] test invariant").headers).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops SecretRef marker headers in inline provider models", () => {
|
||||
@@ -281,7 +289,7 @@ describe("buildInlineProviderModels", () => {
|
||||
const result = buildInlineProviderModels(providers);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].headers).toEqual({
|
||||
expect(expectDefined(result[0], "result[0] test invariant").headers).toEqual({
|
||||
"X-Static": "tenant-a",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Coverage for replay-safe Codex app-server recovery retries.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { makeModelFallbackCfg } from "../test-helpers/model-fallback-config-fixture.js";
|
||||
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
|
||||
@@ -273,7 +275,10 @@ describe("runEmbeddedAgent Codex app-server recovery", () => {
|
||||
|
||||
expect(
|
||||
(
|
||||
mockedRunEmbeddedAttempt.mock.calls[1][0] as {
|
||||
expectDefined(
|
||||
mockedRunEmbeddedAttempt.mock.calls[1],
|
||||
"mockedRunEmbeddedAttempt.mock.calls[1] test invariant",
|
||||
)[0] as {
|
||||
suppressNextUserMessagePersistence?: boolean;
|
||||
}
|
||||
).suppressNextUserMessagePersistence,
|
||||
@@ -304,7 +309,10 @@ describe("runEmbeddedAgent Codex app-server recovery", () => {
|
||||
|
||||
expect(
|
||||
(
|
||||
mockedRunEmbeddedAttempt.mock.calls[1][0] as {
|
||||
expectDefined(
|
||||
mockedRunEmbeddedAttempt.mock.calls[1],
|
||||
"mockedRunEmbeddedAttempt.mock.calls[1] test invariant",
|
||||
)[0] as {
|
||||
suppressNextUserMessagePersistence?: boolean;
|
||||
}
|
||||
).suppressNextUserMessagePersistence,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createReplyOperation } from "../../auto-reply/reply/reply-run-registry.js";
|
||||
import {
|
||||
@@ -1578,9 +1579,14 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => {
|
||||
expectRecordFields(authProfiles["openai:work"], { provider: "openai" });
|
||||
expect(harnessParams.toolAuthProfileStore).toBe(codexAuthStore);
|
||||
expect(mockedMarkAuthProfileSuccess).toHaveBeenCalledTimes(1);
|
||||
const [[successParams]] = mockedMarkAuthProfileSuccess.mock.calls as unknown as Array<
|
||||
[{ provider?: string; profileId?: string }]
|
||||
>;
|
||||
const [successParams] = expectDefined(
|
||||
(
|
||||
mockedMarkAuthProfileSuccess.mock.calls as unknown as Array<
|
||||
[{ provider?: string; profileId?: string }]
|
||||
>
|
||||
)[0],
|
||||
"(mockedMarkAuthProfileSuccess.mock.calls as unknown as Array<\n [{ provider?: string; profileId?: string }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(successParams.provider).toBe("openai");
|
||||
expect(successParams.profileId).toBe("openai:work");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Coverage for normalizing tool calls before and during model replay.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -163,7 +165,9 @@ describe("wrapStreamFnPromoteStandaloneTextToolCalls", () => {
|
||||
arguments: { command: "cat /proc/mounts 2>/dev/null | head -20" },
|
||||
partialArgs: '{"command":"cat /proc/mounts 2>/dev/null | head -20"}',
|
||||
});
|
||||
expect(String(content[1].id)).toMatch(/^call_[a-f0-9]{24}$/);
|
||||
expect(String(expectDefined(content[1], "content[1] test invariant").id)).toMatch(
|
||||
/^call_[a-f0-9]{24}$/,
|
||||
);
|
||||
expect(content[2]).toMatchObject({
|
||||
type: "toolCall",
|
||||
name: "exec",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { notifyLlmRequestActivity } from "@openclaw/ai/internal/runtime";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
// LLM idle-timeout tests cover timeout selection and stream wrapping for
|
||||
// embedded provider calls, including local-provider and cron exceptions.
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
@@ -674,7 +675,10 @@ describe("streamWithIdleTimeout", () => {
|
||||
return {
|
||||
async next() {
|
||||
if (index < chunks.length) {
|
||||
return { done: false, value: chunks[index++] };
|
||||
return {
|
||||
done: false,
|
||||
value: expectDefined(chunks[index++], "chunks[index++] test invariant"),
|
||||
};
|
||||
}
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* with no pending tool calls, so the parent session is idle when subagent
|
||||
* results arrive.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
|
||||
import {
|
||||
@@ -80,8 +82,9 @@ describe("sessions_yield orchestration", () => {
|
||||
|
||||
// clientToolCalls wins — tool_calls stopReason, pendingToolCalls populated
|
||||
expect(result.meta.stopReason).toBe("tool_calls");
|
||||
expect(result.meta.pendingToolCalls).toHaveLength(1);
|
||||
expect(result.meta.pendingToolCalls![0].name).toBe("hosted_tool");
|
||||
const pendingToolCalls = expectDefined(result.meta.pendingToolCalls, "pending tool calls");
|
||||
expect(pendingToolCalls).toHaveLength(1);
|
||||
expect(expectDefined(pendingToolCalls[0], "hosted tool call").name).toBe("hosted_tool");
|
||||
});
|
||||
|
||||
it("preserves order across multiple client tool calls in one attempt (#52288)", async () => {
|
||||
@@ -105,13 +108,16 @@ describe("sessions_yield orchestration", () => {
|
||||
});
|
||||
|
||||
expect(result.meta.stopReason).toBe("tool_calls");
|
||||
expect(result.meta.pendingToolCalls).toHaveLength(3);
|
||||
expect(result.meta.pendingToolCalls!.map((c) => c.name)).toEqual([
|
||||
const pendingToolCalls = expectDefined(result.meta.pendingToolCalls, "pending tool calls");
|
||||
expect(pendingToolCalls).toHaveLength(3);
|
||||
expect(pendingToolCalls.map((c) => c.name)).toEqual([
|
||||
"create_graph",
|
||||
"activate_graph",
|
||||
"get_status",
|
||||
]);
|
||||
expect(JSON.parse(result.meta.pendingToolCalls![0].arguments)).toEqual({
|
||||
expect(
|
||||
JSON.parse(expectDefined(pendingToolCalls[0], "first pending tool call").arguments),
|
||||
).toEqual({
|
||||
nodes: ["a", "b"],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tool-result context guard tests cover live replay truncation, mid-turn
|
||||
// prechecks, and context-engine loop hooks for oversized tool outputs.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { ContextEngine, ContextEngineRuntimeSettings } from "../../context-engine/types.js";
|
||||
@@ -215,10 +217,16 @@ describe("installToolResultContextGuard", () => {
|
||||
const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[];
|
||||
|
||||
expect(transformed).not.toBe(contextForNextCall);
|
||||
const newResultText = getToolResultText(transformed[0]);
|
||||
const newResultText = getToolResultText(
|
||||
expectDefined(transformed[0], "transformed[0] test invariant"),
|
||||
);
|
||||
expect(newResultText.length).toBeLessThan(5_000);
|
||||
expectOpenClawTruncation(newResultText);
|
||||
expect(getToolResultText(contextForNextCall[0])).toBe("z".repeat(5_000));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[0], "contextForNextCall[0] test invariant"),
|
||||
),
|
||||
).toBe("z".repeat(5_000));
|
||||
});
|
||||
|
||||
it("wraps an existing transformContext and guards the transformed output", async () => {
|
||||
@@ -234,7 +242,9 @@ describe("installToolResultContextGuard", () => {
|
||||
const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[];
|
||||
|
||||
expect(transformed).not.toBe(contextForNextCall);
|
||||
expectOpenClawTruncation(getToolResultText(transformed[0]));
|
||||
expectOpenClawTruncation(
|
||||
getToolResultText(expectDefined(transformed[0], "transformed[0] test invariant")),
|
||||
);
|
||||
});
|
||||
|
||||
it("handles legacy role=tool string outputs with truncation wording", async () => {
|
||||
@@ -242,7 +252,9 @@ describe("installToolResultContextGuard", () => {
|
||||
const contextForNextCall = [makeLegacyToolResult("call_big", "y".repeat(5_000))];
|
||||
|
||||
const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[];
|
||||
const newResultText = getToolResultText(transformed[0]);
|
||||
const newResultText = getToolResultText(
|
||||
expectDefined(transformed[0], "transformed[0] test invariant"),
|
||||
);
|
||||
|
||||
expect(typeof (transformed[0] as { content?: unknown }).content).toBe("string");
|
||||
expectOpenClawTruncation(newResultText);
|
||||
@@ -256,7 +268,9 @@ describe("installToolResultContextGuard", () => {
|
||||
|
||||
const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[];
|
||||
const result = transformed[0] as { details?: unknown };
|
||||
const newResultText = getToolResultText(transformed[0]);
|
||||
const newResultText = getToolResultText(
|
||||
expectDefined(transformed[0], "transformed[0] test invariant"),
|
||||
);
|
||||
|
||||
expectOpenClawTruncation(newResultText);
|
||||
expect(result.details).toBeUndefined();
|
||||
@@ -279,7 +293,11 @@ describe("installToolResultContextGuard", () => {
|
||||
await expect(applyGuardToContext(agent, contextForNextCall)).rejects.toThrow(
|
||||
PREEMPTIVE_CONTEXT_OVERFLOW_MESSAGE,
|
||||
);
|
||||
expect(getToolResultText(contextForNextCall[1])).toBe("x".repeat(5_000));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[1], "contextForNextCall[1] test invariant"),
|
||||
),
|
||||
).toBe("x".repeat(5_000));
|
||||
});
|
||||
|
||||
it("throws instead of rewriting older tool results under aggregate pressure", async () => {
|
||||
@@ -294,9 +312,21 @@ describe("installToolResultContextGuard", () => {
|
||||
await expect(applyGuardToContext(agent, contextForNextCall)).rejects.toThrow(
|
||||
PREEMPTIVE_CONTEXT_OVERFLOW_MESSAGE,
|
||||
);
|
||||
expect(getToolResultText(contextForNextCall[1])).toBe("a".repeat(500));
|
||||
expect(getToolResultText(contextForNextCall[2])).toBe("b".repeat(500));
|
||||
expect(getToolResultText(contextForNextCall[3])).toBe("c".repeat(500));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[1], "contextForNextCall[1] test invariant"),
|
||||
),
|
||||
).toBe("a".repeat(500));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[2], "contextForNextCall[2] test invariant"),
|
||||
),
|
||||
).toBe("b".repeat(500));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[3], "contextForNextCall[3] test invariant"),
|
||||
),
|
||||
).toBe("c".repeat(500));
|
||||
});
|
||||
|
||||
it("does not special-case the latest read result before throwing under aggregate pressure", async () => {
|
||||
@@ -310,8 +340,16 @@ describe("installToolResultContextGuard", () => {
|
||||
await expect(applyGuardToContext(agent, contextForNextCall)).rejects.toThrow(
|
||||
PREEMPTIVE_CONTEXT_OVERFLOW_MESSAGE,
|
||||
);
|
||||
expect(getToolResultText(contextForNextCall[1])).toBe("x".repeat(400));
|
||||
expect(getToolResultText(contextForNextCall[2])).toBe("y".repeat(500));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[1], "contextForNextCall[1] test invariant"),
|
||||
),
|
||||
).toBe("x".repeat(400));
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[2], "contextForNextCall[2] test invariant"),
|
||||
),
|
||||
).toBe("y".repeat(500));
|
||||
});
|
||||
|
||||
it("supports model-window-specific truncation for large but otherwise valid tool results", async () => {
|
||||
@@ -324,7 +362,9 @@ describe("installToolResultContextGuard", () => {
|
||||
100_000,
|
||||
)) as AgentMessage[];
|
||||
|
||||
expectOpenClawTruncation(getToolResultText(transformed[0]));
|
||||
expectOpenClawTruncation(
|
||||
getToolResultText(expectDefined(transformed[0], "transformed[0] test invariant")),
|
||||
);
|
||||
});
|
||||
|
||||
it("truncates UTF-16 tool results without splitting surrogate pairs", async () => {
|
||||
@@ -341,10 +381,14 @@ describe("installToolResultContextGuard", () => {
|
||||
1_000,
|
||||
)) as AgentMessage[];
|
||||
|
||||
expect(getToolResultText(transformed[0])).toBe(
|
||||
expect(getToolResultText(expectDefined(transformed[0], "transformed[0] test invariant"))).toBe(
|
||||
"a".repeat(439) + formatContextLimitTruncationNotice(1_002),
|
||||
);
|
||||
expect(getToolResultText(contextForNextCall[0])).toBe(text);
|
||||
expect(
|
||||
getToolResultText(
|
||||
expectDefined(contextForNextCall[0], "contextForNextCall[0] test invariant"),
|
||||
),
|
||||
).toBe(text);
|
||||
});
|
||||
|
||||
it("raises a structured mid-turn precheck signal after a new tool result overflows", async () => {
|
||||
@@ -419,8 +463,12 @@ describe("installToolResultContextGuard", () => {
|
||||
const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[];
|
||||
|
||||
expect(transformed).toBe(contextForNextCall);
|
||||
expect(getToolResultText(transformed[0])).toBe("x".repeat(100));
|
||||
expect(getToolResultText(transformed[1])).toBe("y".repeat(120));
|
||||
expect(getToolResultText(expectDefined(transformed[0], "transformed[0] test invariant"))).toBe(
|
||||
"x".repeat(100),
|
||||
);
|
||||
expect(getToolResultText(expectDefined(transformed[1], "transformed[1] test invariant"))).toBe(
|
||||
"y".repeat(120),
|
||||
);
|
||||
expect((contextForNextCall[0] as { details?: unknown }).details).toBeDefined();
|
||||
expect((contextForNextCall[1] as { details?: unknown }).details).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import type { AssistantMessage, ToolResultMessage, UserMessage } from "openclaw/plugin-sdk/llm";
|
||||
@@ -1638,8 +1639,8 @@ describe("truncateOversizedToolResultsInSession", () => {
|
||||
);
|
||||
|
||||
expect(toolTexts[0]).toContain("truncated");
|
||||
expect(toolTexts[1].length).toBeGreaterThan(0);
|
||||
expect(toolTexts[2].length).toBeGreaterThan(0);
|
||||
expect(expectDefined(toolTexts[1], "toolTexts[1] test invariant").length).toBeGreaterThan(0);
|
||||
expect(expectDefined(toolTexts[2], "toolTexts[2] test invariant").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("lets aggregate recovery honor a tiny explicit cap during persisted rewrite", async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -168,15 +169,18 @@ describe("rewriteTranscriptEntriesInSessionManager", () => {
|
||||
it("branches from the first replaced message and re-appends the remaining suffix", () => {
|
||||
const { sessionManager, toolResultEntryId } = createReadRewriteSession();
|
||||
|
||||
const result = rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: toolResultEntryId,
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = expectDefined(
|
||||
rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: expectDefined(toolResultEntryId, "toolResultEntryId test invariant"),
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
}),
|
||||
"rewriteTranscriptEntriesInSessionManager({ sessionManager, replacemen... test invariant",
|
||||
);
|
||||
|
||||
expect(result.changed).toBe(true);
|
||||
expect(result.rewrittenEntries).toBe(1);
|
||||
@@ -203,15 +207,18 @@ describe("rewriteTranscriptEntriesInSessionManager", () => {
|
||||
);
|
||||
sessionManager.appendLabelChange(summaryEntry.id, "bookmark");
|
||||
|
||||
const result = rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: toolResultEntryId,
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = expectDefined(
|
||||
rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: expectDefined(toolResultEntryId, "toolResultEntryId test invariant"),
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
}),
|
||||
"rewriteTranscriptEntriesInSessionManager({ sessionManager, replacemen... test invariant",
|
||||
);
|
||||
|
||||
expect(result.changed).toBe(true);
|
||||
const rewrittenSummaryEntry = requireValue(
|
||||
@@ -230,17 +237,24 @@ describe("rewriteTranscriptEntriesInSessionManager", () => {
|
||||
toolResultEntryId,
|
||||
tailAssistantEntryId: keptAssistantEntryId,
|
||||
} = createReadRewriteSession({ tailAssistantText: "keep me" });
|
||||
sessionManager.appendCompaction("summary", keptAssistantEntryId, 123);
|
||||
sessionManager.appendCompaction(
|
||||
"summary",
|
||||
expectDefined(keptAssistantEntryId, "keptAssistantEntryId test invariant"),
|
||||
123,
|
||||
);
|
||||
|
||||
const result = rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: toolResultEntryId,
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = expectDefined(
|
||||
rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: expectDefined(toolResultEntryId, "toolResultEntryId test invariant"),
|
||||
message: createToolResultReplacement("read", "[externalized file_123]", 3),
|
||||
},
|
||||
],
|
||||
}),
|
||||
"rewriteTranscriptEntriesInSessionManager({ sessionManager, replacemen... test invariant",
|
||||
);
|
||||
|
||||
expect(result.changed).toBe(true);
|
||||
const branch = sessionManager.getBranch();
|
||||
@@ -273,15 +287,18 @@ describe("rewriteTranscriptEntriesInSessionManager", () => {
|
||||
message.role === "assistant" ? { block: true } : undefined,
|
||||
});
|
||||
|
||||
const result = rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: toolResultEntryId,
|
||||
message: createToolResultReplacement("exec", "[exact replacement]", 2),
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = expectDefined(
|
||||
rewriteTranscriptEntriesInSessionManager({
|
||||
sessionManager,
|
||||
replacements: [
|
||||
{
|
||||
entryId: expectDefined(toolResultEntryId, "toolResultEntryId test invariant"),
|
||||
message: createToolResultReplacement("exec", "[exact replacement]", 2),
|
||||
},
|
||||
],
|
||||
}),
|
||||
"rewriteTranscriptEntriesInSessionManager({ sessionManager, replacemen... test invariant",
|
||||
);
|
||||
|
||||
expect(result.changed).toBe(true);
|
||||
const branchMessages = getBranchMessages(sessionManager);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tool subscription helper tests cover error extraction, sanitized tool results,
|
||||
// and safe lifecycle payloads for embedded tool events.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import * as loggingConfigModule from "../logging/config.js";
|
||||
import {
|
||||
@@ -207,7 +209,7 @@ describe("isToolResultError", () => {
|
||||
function getTextContent(result: unknown, index = 0): string {
|
||||
// Sanitizer tests assert text redaction while keeping the result shape opaque.
|
||||
const record = result as { content: Array<{ text: string }> };
|
||||
return record.content[index].text;
|
||||
return expectDefined(record.content[index], "record.content[index] test invariant").text;
|
||||
}
|
||||
|
||||
describe("sanitizeToolResult", () => {
|
||||
@@ -304,9 +306,15 @@ describe("sanitizeToolResult", () => {
|
||||
const sanitized = sanitizeToolResult(result) as {
|
||||
content: Array<{ data?: string; bytes?: number; omitted?: boolean }>;
|
||||
};
|
||||
expect(sanitized.content[0].data).toBeUndefined();
|
||||
expect(sanitized.content[0].omitted).toBe(true);
|
||||
expect(sanitized.content[0].bytes).toBe("base64imagedata".length);
|
||||
expect(
|
||||
expectDefined(sanitized.content[0], "sanitized.content[0] test invariant").data,
|
||||
).toBeUndefined();
|
||||
expect(expectDefined(sanitized.content[0], "sanitized.content[0] test invariant").omitted).toBe(
|
||||
true,
|
||||
);
|
||||
expect(expectDefined(sanitized.content[0], "sanitized.content[0] test invariant").bytes).toBe(
|
||||
"base64imagedata".length,
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts secrets inside result.details (e.g. exec aggregated stdout)", () => {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Regression coverage for internal runtime-context stripping and extraction.
|
||||
* Verifies protected delimiters, legacy blocks, and custom-message filtering.
|
||||
*/
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
escapeInternalRuntimeContextDelimiters,
|
||||
@@ -119,7 +121,10 @@ describe("internal runtime context codec", () => {
|
||||
const lineCount = 4 + Math.floor(rng() * 12);
|
||||
const payloadLines: string[] = [];
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const token = tokenPool[Math.floor(rng() * tokenPool.length)];
|
||||
const token = expectDefined(
|
||||
tokenPool[Math.floor(rng() * tokenPool.length)],
|
||||
"tokenPool[Math.floor(rng() * tokenPool.length)] test invariant",
|
||||
);
|
||||
payloadLines.push(token);
|
||||
}
|
||||
const escapedPayload = payloadLines.map((line) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Tests auth-profile backed MCP bearer projection. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveMcpBearerBundleConfig, withMcpAuthProfileBearer } from "./mcp-auth-profile.js";
|
||||
import * as mcpHttpFetch from "./mcp-http-fetch.js";
|
||||
@@ -148,7 +149,10 @@ describe("mcp auth profile bearer projection", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const server = resolved.config.mcpServers.ducktape;
|
||||
const server = expectDefined(
|
||||
resolved.config.mcpServers.ducktape,
|
||||
"resolved.config.mcpServers.ducktape test invariant",
|
||||
);
|
||||
expect(server.auth).toBeUndefined();
|
||||
expect(server.oauth).toBeUndefined();
|
||||
expect(server.headers).toEqual({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Covers model fallback ordering, error classification, and auth cooldown behavior.
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { TranscriptNotContinuableError } from "../../packages/agent-core/src/errors.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
@@ -904,8 +905,12 @@ describe("runWithModelFallback", () => {
|
||||
expect(result.result).toBe("ok");
|
||||
expect(run).toHaveBeenCalledTimes(2);
|
||||
expect(result.attempts).toHaveLength(1);
|
||||
expect(result.attempts[0].error).toBe("bad request");
|
||||
expect(result.attempts[0].reason).toBe("unknown");
|
||||
expect(expectDefined(result.attempts[0], "result.attempts[0] test invariant").error).toBe(
|
||||
"bad request",
|
||||
);
|
||||
expect(expectDefined(result.attempts[0], "result.attempts[0] test invariant").reason).toBe(
|
||||
"unknown",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not treat Codex missing tool-result failures as model fallback candidates", async () => {
|
||||
@@ -986,7 +991,9 @@ describe("runWithModelFallback", () => {
|
||||
{ isFinalFallbackAttempt: false },
|
||||
]);
|
||||
expect(result.attempts).toHaveLength(1);
|
||||
expect(result.attempts[0].reason).toBe("overloaded");
|
||||
expect(expectDefined(result.attempts[0], "result.attempts[0] test invariant").reason).toBe(
|
||||
"overloaded",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not prepare agent harness plugins for forced OpenClaw candidates", async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Verifies generated models.json preserves source secret markers from runtime snapshots.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
|
||||
@@ -182,9 +183,13 @@ function createOpenAiHeaderRuntimeConfig(): OpenClawConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function getOpenAiProvider(config: OpenClawConfig) {
|
||||
return expectDefined(config.models?.providers?.openai, "OpenAI provider config");
|
||||
}
|
||||
|
||||
function createOpenAiSourceConfigWithHeadersAndApiKey(): OpenClawConfig {
|
||||
const config = createOpenAiHeaderSourceConfig();
|
||||
config.models!.providers!.openai.apiKey = {
|
||||
getOpenAiProvider(config).apiKey = {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OPENAI_API_KEY", // pragma: allowlist secret
|
||||
@@ -194,7 +199,7 @@ function createOpenAiSourceConfigWithHeadersAndApiKey(): OpenClawConfig {
|
||||
|
||||
function createOpenAiRuntimeConfigWithHeadersAndApiKey(): OpenClawConfig {
|
||||
const config = createOpenAiHeaderRuntimeConfig();
|
||||
config.models!.providers!.openai.apiKey = "sk-runtime-resolved"; // pragma: allowlist secret
|
||||
getOpenAiProvider(config).apiKey = "sk-runtime-resolved"; // pragma: allowlist secret
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -263,7 +268,7 @@ describe("models-config runtime source snapshot", () => {
|
||||
const sourceConfig: OpenClawConfig = {
|
||||
models: {
|
||||
providers: {
|
||||
openai: createOpenAiApiKeySourceConfig().models!.providers!.openai,
|
||||
openai: getOpenAiProvider(createOpenAiApiKeySourceConfig()),
|
||||
moonshot: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: { source: "file", provider: "vault", id: "/moonshot/apiKey" },
|
||||
@@ -276,7 +281,7 @@ describe("models-config runtime source snapshot", () => {
|
||||
const runtimeConfig: OpenClawConfig = {
|
||||
models: {
|
||||
providers: {
|
||||
openai: createOpenAiApiKeyRuntimeConfig().models!.providers!.openai,
|
||||
openai: getOpenAiProvider(createOpenAiApiKeyRuntimeConfig()),
|
||||
moonshot: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "sk-runtime-moonshot", // pragma: allowlist secret
|
||||
@@ -349,7 +354,7 @@ describe("models-config runtime source snapshot", () => {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
...runtimeConfig.models!.providers!.openai,
|
||||
...getOpenAiProvider(runtimeConfig),
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
headers: {
|
||||
"X-OpenClaw-Test": "one",
|
||||
@@ -363,7 +368,7 @@ describe("models-config runtime source snapshot", () => {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
...runtimeConfig.models!.providers!.openai,
|
||||
...getOpenAiProvider(runtimeConfig),
|
||||
baseUrl: "https://mirror.example/v1",
|
||||
headers: {
|
||||
"X-OpenClaw-Test": "two",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Live-sweeps discovered model profiles with optional provider/model filters and probes.
|
||||
import { writeSync } from "node:fs";
|
||||
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { type Api, completeSimple, type Model } from "openclaw/plugin-sdk/llm";
|
||||
import { Type } from "typebox";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
@@ -525,7 +526,13 @@ function isIpv4PrivateRange(host: string): boolean {
|
||||
return false;
|
||||
}
|
||||
const [a, b] = octets;
|
||||
return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
|
||||
return (
|
||||
a === 10 ||
|
||||
(a === 172 &&
|
||||
expectDefined(b, "b test invariant") >= 16 &&
|
||||
expectDefined(b, "b test invariant") <= 31) ||
|
||||
(a === 192 && b === 168)
|
||||
);
|
||||
}
|
||||
|
||||
function isIpv6LocalRange(host: string): boolean {
|
||||
@@ -1963,13 +1970,14 @@ describeLive("live models (profile keys)", () => {
|
||||
const attemptMax =
|
||||
model.provider === "anthropic" && anthropicKeys.length > 0 ? anthropicKeys.length : 1;
|
||||
for (let attempt = 0; attempt < attemptMax; attempt += 1) {
|
||||
if (model.provider === "anthropic" && anthropicKeys.length > 0) {
|
||||
process.env.ANTHROPIC_API_KEY = anthropicKeys[attempt];
|
||||
}
|
||||
const apiKey =
|
||||
const anthropicApiKey =
|
||||
model.provider === "anthropic" && anthropicKeys.length > 0
|
||||
? anthropicKeys[attempt]
|
||||
: requireApiKey(apiKeyInfo, model.provider);
|
||||
? expectDefined(anthropicKeys[attempt], `Anthropic API key ${attempt + 1}`)
|
||||
: undefined;
|
||||
if (anthropicApiKey) {
|
||||
process.env.ANTHROPIC_API_KEY = anthropicApiKey;
|
||||
}
|
||||
const apiKey = anthropicApiKey ?? requireApiKey(apiKeyInfo, model.provider);
|
||||
try {
|
||||
// Special regression: OpenAI requires replayed `reasoning` items for tool-only turns.
|
||||
if (
|
||||
@@ -2119,12 +2127,15 @@ describeLive("live models (profile keys)", () => {
|
||||
}
|
||||
|
||||
logProgress(`${progressLabel}: prompt`);
|
||||
const ok = await completeOkWithRetry({
|
||||
model,
|
||||
apiKey,
|
||||
timeoutMs: perModelTimeoutMs,
|
||||
progressLabel,
|
||||
});
|
||||
const ok = expectDefined(
|
||||
await completeOkWithRetry({
|
||||
model,
|
||||
apiKey,
|
||||
timeoutMs: perModelTimeoutMs,
|
||||
progressLabel,
|
||||
}),
|
||||
`${progressLabel} completion result`,
|
||||
);
|
||||
|
||||
if (ok.res.stopReason === "error") {
|
||||
const msg = ok.res.errorMessage ?? "";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests connected node-hosted plugin tool materialization. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { NodePluginToolDescriptor } from "../../packages/gateway-protocol/src/index.js";
|
||||
import {
|
||||
@@ -61,11 +63,13 @@ describe("createNodePluginTools", () => {
|
||||
});
|
||||
|
||||
const tools = createNodePluginTools({ existingToolNames: new Set(["read"]) });
|
||||
const result = await tools[0].execute("call-1", { text: "ping" });
|
||||
const result = await expectDefined(tools[0], "tools[0] test invariant").execute("call-1", {
|
||||
text: "ping",
|
||||
});
|
||||
|
||||
expect(tools.map((tool) => tool.name)).toEqual(["remote_echo"]);
|
||||
expect(tools[0].description).toContain("Studio Node");
|
||||
expect(getPluginToolMeta(tools[0])).toMatchObject({
|
||||
expect(expectDefined(tools[0], "tools[0] test invariant").description).toContain("Studio Node");
|
||||
expect(getPluginToolMeta(expectDefined(tools[0], "tools[0] test invariant"))).toMatchObject({
|
||||
pluginId: "remote-demo",
|
||||
mcp: {
|
||||
serverName: "remote-demo",
|
||||
@@ -112,7 +116,10 @@ describe("createNodePluginTools", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const tool = createNodePluginTools({})[0];
|
||||
const tool = expectDefined(
|
||||
createNodePluginTools({})[0],
|
||||
"createNodePluginTools({})[0] test invariant",
|
||||
);
|
||||
const result = await tool.execute("call-mcp", { query: "needle" });
|
||||
|
||||
expect(callGatewayTool).toHaveBeenCalledWith(
|
||||
@@ -187,7 +194,9 @@ describe("createNodePluginTools", () => {
|
||||
});
|
||||
|
||||
const tools = createNodePluginTools({});
|
||||
const result = await tools[1].execute("call-2", { text: "ping" });
|
||||
const result = await expectDefined(tools[1], "tools[1] test invariant").execute("call-2", {
|
||||
text: "ping",
|
||||
});
|
||||
|
||||
expect(tools.map((tool) => tool.name)).toEqual(["node_a_remote_echo", "node_b_remote_echo"]);
|
||||
expect(callGatewayTool).toHaveBeenCalledWith(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Verifies OpenAI-compatible streaming payloads, failures, and transport wrapping.
|
||||
import { createServer } from "node:http";
|
||||
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import OpenAI from "openai";
|
||||
import type { ChatCompletionChunk } from "openai/resources/chat/completions.js";
|
||||
import type { Api, Model } from "openclaw/plugin-sdk/llm";
|
||||
@@ -6359,7 +6360,9 @@ describe("openai transport stream", () => {
|
||||
nested: { keep: "value" },
|
||||
});
|
||||
expect(stripped.input[0]).not.toHaveProperty("encrypted_content");
|
||||
expect(stripped.input[0].nested).not.toHaveProperty("encrypted_content");
|
||||
expect(
|
||||
expectDefined(stripped.input[0], "stripped.input[0] test invariant").nested,
|
||||
).not.toHaveProperty("encrypted_content");
|
||||
expect(stripped.input[1]).toEqual(params.input[1]);
|
||||
});
|
||||
|
||||
@@ -8199,7 +8202,10 @@ describe("openai transport stream", () => {
|
||||
) as { reasoning_effort?: unknown; tools?: unknown };
|
||||
|
||||
expect(params.tools).toHaveLength(1);
|
||||
const tool = (params.tools as Array<Record<string, unknown>>)[0];
|
||||
const tool = expectDefined(
|
||||
(params.tools as Array<Record<string, unknown>>)[0],
|
||||
"(params.tools as Array<Record<string, unknown>>)[0] test invariant",
|
||||
);
|
||||
expectRecordFields(tool, { type: "function" });
|
||||
expectRecordFields(tool.function, { name: "lookup_weather" });
|
||||
expect(params).not.toHaveProperty("reasoning_effort");
|
||||
@@ -12077,7 +12083,7 @@ describe("openai transport stream", () => {
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-completions">;
|
||||
|
||||
const output = {
|
||||
const output: OpenAICompletionsOutput = {
|
||||
role: "assistant" as const,
|
||||
content: [],
|
||||
api: model.api,
|
||||
@@ -12151,8 +12157,14 @@ describe("openai transport stream", () => {
|
||||
|
||||
await testing.processOpenAICompletionsStream(mockStream(), output, model, stream);
|
||||
|
||||
const thinkingBlock = output.content[0] as { type: string; thinking: string };
|
||||
const textBlock = output.content[1] as { type: string; text: string };
|
||||
const thinkingBlock = expectDefined(output.content[0], "output.content[0] test invariant") as {
|
||||
type: string;
|
||||
thinking: string;
|
||||
};
|
||||
const textBlock = expectDefined(output.content[1], "output.content[1] test invariant") as {
|
||||
type: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
expect(output.content.length).toBe(2);
|
||||
expect(thinkingBlock.type).toBe("thinking");
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Verifies session status output across scoped stores, tasks, and runtime hooks.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveSessionStoreEntry } from "../config/sessions/store-entry.js";
|
||||
import { mergeSessionEntry, type SessionEntry } from "../config/sessions/types.js";
|
||||
@@ -1279,7 +1281,10 @@ describe("session_status tool", () => {
|
||||
expect(details.modelOverride).toBe("anthropic/claude-sonnet-4-6");
|
||||
expect(updateSessionStoreMock).toHaveBeenCalledTimes(1);
|
||||
const savedStore = latestMockCallArg(updateSessionStoreMock, 1) as Record<string, SessionEntry>;
|
||||
const saved = savedStore["agent:main:scope:scopy:direct:scopy"];
|
||||
const saved = expectDefined(
|
||||
savedStore["agent:main:scope:scopy:direct:scopy"],
|
||||
'savedStore["agent:main:scope:scopy:direct:scopy"] test invariant',
|
||||
);
|
||||
expectRecordFields(saved, {
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-sonnet-4-6",
|
||||
@@ -1342,7 +1347,7 @@ describe("session_status tool", () => {
|
||||
});
|
||||
|
||||
await vi.waitFor(() => expect(events).toHaveLength(1));
|
||||
const event = events[0];
|
||||
const event = expectDefined(events[0], "events[0] test invariant");
|
||||
expect(event.type).toBe("session");
|
||||
expect(event.action).toBe("patch");
|
||||
expect(event.sessionKey).toBe("main");
|
||||
@@ -1398,7 +1403,10 @@ describe("session_status tool", () => {
|
||||
expect(details.sessionKey).toBe("agent:main:scope:scopy:direct:scopy");
|
||||
expect(updateSessionStoreMock).toHaveBeenCalledTimes(1);
|
||||
const savedStore = latestMockCallArg(updateSessionStoreMock, 1) as Record<string, SessionEntry>;
|
||||
const saved = savedStore["agent:main:scope:scopy:direct:scopy"];
|
||||
const saved = expectDefined(
|
||||
savedStore["agent:main:scope:scopy:direct:scopy"],
|
||||
'savedStore["agent:main:scope:scopy:direct:scopy"] test invariant',
|
||||
);
|
||||
expectRecordFields(saved, {
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-sonnet-4-6",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Verifies embedded runtime outcome classifications drive model fallback correctly.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import {
|
||||
createContractRunResult,
|
||||
OUTCOME_FALLBACK_RUNTIME_CONTRACT,
|
||||
@@ -403,7 +405,7 @@ describe("Outcome/fallback runtime contract - embedded runtime fallback classifi
|
||||
});
|
||||
|
||||
it("keeps running on the primary when terminal output is not classified as fallback", async () => {
|
||||
const contractCase = nonFallbackCases[0];
|
||||
const contractCase = expectDefined(nonFallbackCases[0], "nonFallbackCases[0] test invariant");
|
||||
const run = vi.fn().mockResolvedValue(contractCase.result);
|
||||
const result = await runWithModelFallback({
|
||||
cfg: undefined,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Runtime plan tool tests cover schema normalization and diagnostics when the
|
||||
// runtime plan owns tool policy, with legacy provider fallback still available.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentTool } from "openclaw/plugin-sdk/agent-core";
|
||||
import {
|
||||
createNativeOpenAIResponsesModel,
|
||||
@@ -228,7 +230,7 @@ describe("AgentRuntimePlan tool policy helpers", () => {
|
||||
});
|
||||
|
||||
expect(result[0]).toBe(normalized);
|
||||
expect(getPluginToolMeta(result[0])).toMatchObject({
|
||||
expect(getPluginToolMeta(expectDefined(result[0], "result[0] test invariant"))).toMatchObject({
|
||||
pluginId: "bundle-mcp",
|
||||
mcp: {
|
||||
serverName: "fixture",
|
||||
@@ -267,8 +269,12 @@ describe("AgentRuntimePlan tool policy helpers", () => {
|
||||
|
||||
expect(result[0]).toBe(normalized);
|
||||
expect((result[0] as AnyAgentTool).catalogMode).toBe("direct-only");
|
||||
expect(isToolWrappedWithBeforeToolCallHook(result[0])).toBe(true);
|
||||
expect(getToolTerminalPresentation(result[0])).toBe(formatter);
|
||||
expect(
|
||||
isToolWrappedWithBeforeToolCallHook(expectDefined(result[0], "result[0] test invariant")),
|
||||
).toBe(true);
|
||||
expect(getToolTerminalPresentation(expectDefined(result[0], "result[0] test invariant"))).toBe(
|
||||
formatter,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reread quarantined tools while preserving normalized metadata", () => {
|
||||
@@ -306,7 +312,7 @@ describe("AgentRuntimePlan tool policy helpers", () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual([normalized]);
|
||||
expect(getPluginToolMeta(result[0])).toMatchObject({
|
||||
expect(getPluginToolMeta(expectDefined(result[0], "result[0] test invariant"))).toMatchObject({
|
||||
pluginId: "bundle-mcp",
|
||||
mcp: {
|
||||
serverName: "fixture",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { OPENCLAW_TRANSCRIPT_ARTIFACT_API } from "../shared/transcript-only-openclaw-assistant.js";
|
||||
import { repairSessionFileIfNeeded } from "./session-file-repair.js";
|
||||
@@ -374,7 +375,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
const repairedLines = repaired.trim().split("\n");
|
||||
expect(repairedLines).toHaveLength(4);
|
||||
const repairedEntry: { message: { content: { type: string; text: string }[] } } = JSON.parse(
|
||||
repairedLines[2],
|
||||
expectDefined(repairedLines[2], "repairedLines[2] test invariant"),
|
||||
);
|
||||
expect(repairedEntry.message.content).toEqual([
|
||||
{ type: "text", text: "[assistant turn failed before producing content]" },
|
||||
@@ -408,7 +409,9 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
const repaired = await fs.readFile(file, "utf-8");
|
||||
const repairedLines = repaired.trim().split("\n");
|
||||
expect(repairedLines).toHaveLength(3);
|
||||
const rewrittenEntry = JSON.parse(repairedLines[1]);
|
||||
const rewrittenEntry = JSON.parse(
|
||||
expectDefined(repairedLines[1], "repairedLines[1] test invariant"),
|
||||
);
|
||||
expect(rewrittenEntry.id).toBe("msg-blank");
|
||||
expect(rewrittenEntry.message.content).toEqual([
|
||||
{ type: "text", text: BLANK_USER_FALLBACK_TEXT },
|
||||
@@ -439,7 +442,9 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
const repaired = await fs.readFile(file, "utf-8");
|
||||
const repairedLines = repaired.trim().split("\n");
|
||||
expect(repairedLines).toHaveLength(3);
|
||||
const rewrittenEntry = JSON.parse(repairedLines[1]);
|
||||
const rewrittenEntry = JSON.parse(
|
||||
expectDefined(repairedLines[1], "repairedLines[1] test invariant"),
|
||||
);
|
||||
expect(rewrittenEntry.message.content).toBe(BLANK_USER_FALLBACK_TEXT);
|
||||
});
|
||||
|
||||
@@ -861,7 +866,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
|
||||
const lines = (await fs.readFile(file, "utf-8")).trimEnd().split("\n");
|
||||
expect(lines).toHaveLength(5);
|
||||
const inserted = JSON.parse(lines[3]);
|
||||
const inserted = JSON.parse(expectDefined(lines[3], "lines[3] test invariant"));
|
||||
expect(inserted.type).toBe("message");
|
||||
expect(inserted.parentId).toBe("msg-asst-process");
|
||||
expect(inserted.message.role).toBe("toolResult");
|
||||
@@ -869,7 +874,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
expect(inserted.message.toolName).toBe("process");
|
||||
expect(inserted.message.isError).toBe(true);
|
||||
expect(inserted.message.content[0].text).toBe("aborted");
|
||||
expect(JSON.parse(lines[4])).toEqual(deliveryMirror);
|
||||
expect(JSON.parse(expectDefined(lines[4], "lines[4] test invariant"))).toEqual(deliveryMirror);
|
||||
});
|
||||
|
||||
it("inserts missing Responses message-tool results before delivery mirrors", async () => {
|
||||
@@ -921,7 +926,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
|
||||
const lines = (await fs.readFile(file, "utf-8")).trimEnd().split("\n");
|
||||
expect(lines).toHaveLength(5);
|
||||
const inserted = JSON.parse(lines[3]);
|
||||
const inserted = JSON.parse(expectDefined(lines[3], "lines[3] test invariant"));
|
||||
expect(inserted.type).toBe("message");
|
||||
expect(inserted.parentId).toBe("msg-asst-message-tool");
|
||||
expect(inserted.message.role).toBe("toolResult");
|
||||
@@ -929,7 +934,7 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
expect(inserted.message.toolName).toBe("message");
|
||||
expect(inserted.message.isError).toBe(true);
|
||||
expect(inserted.message.content[0].text).toBe("aborted");
|
||||
expect(JSON.parse(lines[4])).toEqual(deliveryMirror);
|
||||
expect(JSON.parse(expectDefined(lines[4], "lines[4] test invariant"))).toEqual(deliveryMirror);
|
||||
});
|
||||
|
||||
it("does not duplicate code-mode tool results that are already persisted", async () => {
|
||||
@@ -1166,8 +1171,8 @@ describe("repairSessionFileIfNeeded", () => {
|
||||
const after = await fs.readFile(file, "utf-8");
|
||||
const lines = after.trimEnd().split("\n");
|
||||
expect(lines).toHaveLength(2);
|
||||
expect(JSON.parse(lines[0])).toEqual(header);
|
||||
expect(JSON.parse(lines[1])).toEqual(message);
|
||||
expect(JSON.parse(expectDefined(lines[0], "lines[0] test invariant"))).toEqual(header);
|
||||
expect(JSON.parse(expectDefined(lines[1], "lines[1] test invariant"))).toEqual(message);
|
||||
expect(after).not.toContain('"role":null');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Verifies session tool-result guard inserts, truncates, and repairs tool results.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { describe, expect, it } from "vitest";
|
||||
@@ -537,7 +539,9 @@ describe("installSessionToolResultGuard", () => {
|
||||
};
|
||||
};
|
||||
const serializedToolResult = JSON.stringify(toolResult);
|
||||
expect(toolResult.content[0].text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(
|
||||
expectDefined(toolResult.content[0], "toolResult.content[0] test invariant").text,
|
||||
).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(serializedToolResult).not.toContain("plainsecretvalue123");
|
||||
expect(serializedToolResult).not.toContain("hunter2");
|
||||
expect(serializedToolResult).not.toContain("nestedplainsecret123");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { uuidQueue } = vi.hoisted(() => ({ uuidQueue: [] as string[] }));
|
||||
@@ -65,8 +66,12 @@ describe("v1 session migration id assignment", () => {
|
||||
expect(messages).toHaveLength(2);
|
||||
const ids = messages.map((m) => m.id);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
expect(messages[1].parentId).toBe(messages[0].id);
|
||||
expect(messages[1].parentId).not.toBe(messages[1].id);
|
||||
expect(expectDefined(messages[1], "messages[1] test invariant").parentId).toBe(
|
||||
expectDefined(messages[0], "messages[0] test invariant").id,
|
||||
);
|
||||
expect(expectDefined(messages[1], "messages[1] test invariant").parentId).not.toBe(
|
||||
expectDefined(messages[1], "messages[1] test invariant").id,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves compaction indexes across opaque rows", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { applyPatch } from "diff";
|
||||
import { Value } from "typebox/value";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -373,7 +374,7 @@ describe("edit tool", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const tc0 = result.content[0];
|
||||
const tc0 = expectDefined(result.content[0], "result.content[0] test invariant");
|
||||
expect("text" in tc0 ? tc0.text : "").toContain("No changes made");
|
||||
expect((result as any).terminate).toBe(true);
|
||||
await expect(fs.readFile(filePath, "utf-8")).resolves.toBe("unchanged content\n");
|
||||
@@ -599,7 +600,7 @@ describe("edit tool", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const tcText = result.content[0];
|
||||
const tcText = expectDefined(result.content[0], "result.content[0] test invariant");
|
||||
expect("text" in tcText ? tcText.text : "").toContain("Successfully replaced");
|
||||
expect((result as any).terminate).toBeFalsy();
|
||||
await expect(fs.readFile(filePath, "utf-8")).resolves.toBe("alpha beta GAMMA\n");
|
||||
@@ -618,7 +619,7 @@ describe("edit tool", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const tc1 = result.content[0];
|
||||
const tc1 = expectDefined(result.content[0], "result.content[0] test invariant");
|
||||
expect("text" in tc1 ? tc1.text : "").toContain("Successfully replaced");
|
||||
await expect(fs.readFile(filePath, "utf-8")).resolves.toBe("new content\n");
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createWriteTool, type WriteOperations } from "./write.js";
|
||||
|
||||
@@ -135,7 +136,7 @@ describe("write tool", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const tc0 = result.content[0];
|
||||
const tc0 = expectDefined(result.content[0], "result.content[0] test invariant");
|
||||
expect("text" in tc0 ? tc0.text : "").toContain("No changes made");
|
||||
expect((result as any).terminate).toBe(true);
|
||||
await expect(fs.readFile(filePath, "utf-8")).resolves.toBe("hello\n");
|
||||
|
||||
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { captureEnv, setTestEnvValue } from "../test-utils/env.js";
|
||||
import {
|
||||
@@ -208,7 +209,10 @@ describe("exec shell snapshots", () => {
|
||||
.filter((entry) => entry.endsWith(".sh"));
|
||||
expect(snapshotFiles).toHaveLength(1);
|
||||
const snapshot = fs.readFileSync(
|
||||
path.join(resolveShellSnapshotDir(env), snapshotFiles[0]),
|
||||
path.join(
|
||||
resolveShellSnapshotDir(env),
|
||||
expectDefined(snapshotFiles[0], "snapshotFiles[0] test invariant"),
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
expect(snapshot).toContain("oc_snap_fn");
|
||||
@@ -396,7 +400,10 @@ describe("exec shell snapshots", () => {
|
||||
.filter((entry) => entry.endsWith(".sh"));
|
||||
expect(snapshotFiles).toHaveLength(1);
|
||||
const snapshot = fs.readFileSync(
|
||||
path.join(resolveShellSnapshotDir(env), snapshotFiles[0]),
|
||||
path.join(
|
||||
resolveShellSnapshotDir(env),
|
||||
expectDefined(snapshotFiles[0], "snapshotFiles[0] test invariant"),
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
expect(snapshot).not.toContain("virtual");
|
||||
@@ -447,7 +454,11 @@ describe("exec shell snapshots", () => {
|
||||
const snapshotFiles = fs.readdirSync(snapshotDir).filter((entry) => entry.endsWith(".sh"));
|
||||
expect(snapshotFiles).toHaveLength(1);
|
||||
const staleTime = new Date(Date.now() - 10 * 60 * 1000);
|
||||
fs.utimesSync(path.join(snapshotDir, snapshotFiles[0]), staleTime, staleTime);
|
||||
fs.utimesSync(
|
||||
path.join(snapshotDir, expectDefined(snapshotFiles[0], "snapshotFiles[0] test invariant")),
|
||||
staleTime,
|
||||
staleTime,
|
||||
);
|
||||
resetShellSnapshotCacheForTests();
|
||||
|
||||
await expect(runAlias()).resolves.toBe("new");
|
||||
@@ -486,7 +497,10 @@ describe("exec shell snapshots", () => {
|
||||
const snapshotDir = resolveShellSnapshotDir(env);
|
||||
const snapshotFiles = fs.readdirSync(snapshotDir).filter((entry) => entry.endsWith(".sh"));
|
||||
expect(snapshotFiles).toHaveLength(1);
|
||||
const snapshotPath = path.join(snapshotDir, snapshotFiles[0]);
|
||||
const snapshotPath = path.join(
|
||||
snapshotDir,
|
||||
expectDefined(snapshotFiles[0], "snapshotFiles[0] test invariant"),
|
||||
);
|
||||
fs.writeFileSync(
|
||||
snapshotPath,
|
||||
[
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Nested subagent registry e2e tests cover requester/controller relationships
|
||||
// across orchestrator and leaf child sessions.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import "./subagent-registry.mocks.shared.js";
|
||||
|
||||
@@ -62,12 +64,12 @@ describe("subagent registry nested agent tracking", () => {
|
||||
// Main sees its direct child (the orchestrator)
|
||||
const mainRuns = listSubagentRunsForRequester("agent:main:main");
|
||||
expect(mainRuns).toHaveLength(1);
|
||||
expect(mainRuns[0].runId).toBe("run-orch");
|
||||
expect(expectDefined(mainRuns[0], "mainRuns[0] test invariant").runId).toBe("run-orch");
|
||||
|
||||
// Orchestrator sees its direct child (the leaf)
|
||||
const orchRuns = listSubagentRunsForRequester("agent:main:subagent:orch-uuid");
|
||||
expect(orchRuns).toHaveLength(1);
|
||||
expect(orchRuns[0].runId).toBe("run-leaf");
|
||||
expect(expectDefined(orchRuns[0], "orchRuns[0] test invariant").runId).toBe("run-leaf");
|
||||
|
||||
// Leaf has no children
|
||||
const leafRuns = listSubagentRunsForRequester(
|
||||
@@ -94,8 +96,12 @@ describe("subagent registry nested agent tracking", () => {
|
||||
const { listSubagentRunsForRequester } = subagentRegistry;
|
||||
const orchRuns = listSubagentRunsForRequester("agent:main:subagent:orch");
|
||||
expect(orchRuns).toHaveLength(1);
|
||||
expect(orchRuns[0].requesterSessionKey).toBe("agent:main:subagent:orch");
|
||||
expect(orchRuns[0].childSessionKey).toBe("agent:main:subagent:orch:subagent:child");
|
||||
expect(expectDefined(orchRuns[0], "orchRuns[0] test invariant").requesterSessionKey).toBe(
|
||||
"agent:main:subagent:orch",
|
||||
);
|
||||
expect(expectDefined(orchRuns[0], "orchRuns[0] test invariant").childSessionKey).toBe(
|
||||
"agent:main:subagent:orch:subagent:child",
|
||||
);
|
||||
});
|
||||
|
||||
it("countActiveRunsForSession only counts active children of the specific session", () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import "./subagent-registry.mocks.shared.js";
|
||||
import { replaceSessionEntry } from "../config/sessions/session-accessor.js";
|
||||
@@ -662,7 +663,10 @@ describe("subagent registry persistence", () => {
|
||||
const afterSecond = JSON.parse(await fs.readFile(registryPath, "utf8")) as {
|
||||
runs: Record<string, { cleanupCompletedAt?: number }>;
|
||||
};
|
||||
expect(afterSecond.runs["run-3"].cleanupCompletedAt).toBeGreaterThanOrEqual(beforeRetry);
|
||||
expect(
|
||||
expectDefined(afterSecond.runs["run-3"], 'afterSecond.runs["run-3"] test invariant')
|
||||
.cleanupCompletedAt,
|
||||
).toBeGreaterThanOrEqual(beforeRetry);
|
||||
});
|
||||
|
||||
it("retries cleanup announce after announce flow rejects", async () => {
|
||||
@@ -692,8 +696,14 @@ describe("subagent registry persistence", () => {
|
||||
const afterFirst = JSON.parse(await fs.readFile(registryPath, "utf8")) as {
|
||||
runs: Record<string, { cleanupHandled?: boolean; cleanupCompletedAt?: number }>;
|
||||
};
|
||||
expect(afterFirst.runs["run-reject"].cleanupHandled).toBe(false);
|
||||
expect(afterFirst.runs["run-reject"].cleanupCompletedAt).toBeUndefined();
|
||||
expect(
|
||||
expectDefined(afterFirst.runs["run-reject"], 'afterFirst.runs["run-reject"] test invariant')
|
||||
.cleanupHandled,
|
||||
).toBe(false);
|
||||
expect(
|
||||
expectDefined(afterFirst.runs["run-reject"], 'afterFirst.runs["run-reject"] test invariant')
|
||||
.cleanupCompletedAt,
|
||||
).toBeUndefined();
|
||||
|
||||
announceSpy.mockResolvedValueOnce(true);
|
||||
const beforeRetry = Date.now();
|
||||
@@ -709,7 +719,10 @@ describe("subagent registry persistence", () => {
|
||||
const afterSecond = JSON.parse(await fs.readFile(registryPath, "utf8")) as {
|
||||
runs: Record<string, { cleanupCompletedAt?: number }>;
|
||||
};
|
||||
expect(afterSecond.runs["run-reject"].cleanupCompletedAt).toBeGreaterThanOrEqual(beforeRetry);
|
||||
expect(
|
||||
expectDefined(afterSecond.runs["run-reject"], 'afterSecond.runs["run-reject"] test invariant')
|
||||
.cleanupCompletedAt,
|
||||
).toBeGreaterThanOrEqual(beforeRetry);
|
||||
});
|
||||
|
||||
it("keeps delete-mode runs retryable when announce is deferred", async () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Subagent registry steer-restart tests cover replacing child runs after steer
|
||||
// commands while preserving lifecycle hooks and completion delivery.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ContextEngine } from "../context-engine/types.js";
|
||||
import {
|
||||
@@ -317,7 +319,7 @@ describe("subagent registry steer restarts", () => {
|
||||
|
||||
const runs = listMainRuns();
|
||||
expect(runs).toHaveLength(1);
|
||||
expect(runs[0].runId).toBe(params.nextRunId);
|
||||
expect(expectDefined(runs[0], "runs[0] test invariant").runId).toBe(params.nextRunId);
|
||||
return runs[0];
|
||||
};
|
||||
|
||||
@@ -487,11 +489,14 @@ describe("subagent registry steer restarts", () => {
|
||||
previous.delivery = { status: "pending", attemptCount: 2, lastAttemptAt: Date.now() };
|
||||
}
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: "run-retry-reset-old",
|
||||
nextRunId: "run-retry-reset-new",
|
||||
fallback: previous,
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-retry-reset-old",
|
||||
nextRunId: "run-retry-reset-new",
|
||||
fallback: previous,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-retry-reset-old", nextRunI... test invariant',
|
||||
);
|
||||
expect(run.delivery?.attemptCount).toBeUndefined();
|
||||
expect(run.delivery?.lastAttemptAt).toBeUndefined();
|
||||
}
|
||||
@@ -514,11 +519,14 @@ describe("subagent registry steer restarts", () => {
|
||||
previous.outcome = { status: "ok" };
|
||||
}
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: "run-terminal-state-old",
|
||||
nextRunId: "run-terminal-state-new",
|
||||
fallback: previous,
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-terminal-state-old",
|
||||
nextRunId: "run-terminal-state-new",
|
||||
fallback: previous,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-terminal-state-old", nextR... test invariant',
|
||||
);
|
||||
expect(run.endedHookEmittedAt).toBeUndefined();
|
||||
expect(run.endedReason).toBeUndefined();
|
||||
|
||||
@@ -554,11 +562,14 @@ describe("subagent registry steer restarts", () => {
|
||||
previous.cleanupHandled = true;
|
||||
}
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: "run-frozen-old",
|
||||
nextRunId: "run-frozen-new",
|
||||
fallback: previous,
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-frozen-old",
|
||||
nextRunId: "run-frozen-new",
|
||||
fallback: previous,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-frozen-old", nextRunId: "r... test invariant',
|
||||
);
|
||||
|
||||
expect(run.completion?.resultText).toBeUndefined();
|
||||
expect(run.completion?.capturedAt).toBeUndefined();
|
||||
@@ -585,12 +596,15 @@ describe("subagent registry steer restarts", () => {
|
||||
expect(previous?.taskRunId).toBe("run-steer-task-old");
|
||||
expect(previous?.generation).toBe(1);
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: "run-steer-task-old",
|
||||
nextRunId: "run-steer-task-new",
|
||||
fallback: previous,
|
||||
task: "new steer instruction from user",
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-steer-task-old",
|
||||
nextRunId: "run-steer-task-new",
|
||||
fallback: previous,
|
||||
task: "new steer instruction from user",
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-steer-task-old", nextRunId... test invariant',
|
||||
);
|
||||
|
||||
expect(run.task).toBe("new steer instruction from user");
|
||||
expect(run.taskRunId).toBe("run-steer-task-old");
|
||||
@@ -611,11 +625,14 @@ describe("subagent registry steer restarts", () => {
|
||||
fallback.generation = 2;
|
||||
mod.releaseSubagentRun(fallback.runId);
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: fallback.runId,
|
||||
nextRunId: "run-fallback-generation-new",
|
||||
fallback,
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: fallback.runId,
|
||||
nextRunId: "run-fallback-generation-new",
|
||||
fallback,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: fallback.runId, nextRunId: "run... test invariant',
|
||||
);
|
||||
|
||||
expect(run.generation).toBe(3);
|
||||
});
|
||||
@@ -633,11 +650,14 @@ describe("subagent registry steer restarts", () => {
|
||||
const previous = listMainRuns()[0];
|
||||
expect(previous?.runId).toBe("run-task-preserve-old");
|
||||
|
||||
const run = replaceRunAfterSteer({
|
||||
previousRunId: "run-task-preserve-old",
|
||||
nextRunId: "run-task-preserve-new",
|
||||
fallback: previous,
|
||||
});
|
||||
const run = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-task-preserve-old",
|
||||
nextRunId: "run-task-preserve-new",
|
||||
fallback: previous,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-task-preserve-old", nextRu... test invariant',
|
||||
);
|
||||
|
||||
expect(run.task).toBe("preserve me verbatim");
|
||||
});
|
||||
@@ -648,19 +668,25 @@ describe("subagent registry steer restarts", () => {
|
||||
childSessionKey: "agent:main:subagent:legacy-owner",
|
||||
task: "legacy owner task",
|
||||
});
|
||||
const first = replaceRunAfterSteer({
|
||||
previousRunId: "run-legacy-owner-original",
|
||||
nextRunId: "run-legacy-owner-restored",
|
||||
});
|
||||
const first = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-legacy-owner-original",
|
||||
nextRunId: "run-legacy-owner-restored",
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-legacy-owner-original", ne... test invariant',
|
||||
);
|
||||
// Pre-change persisted replacement rows did not record taskRunId.
|
||||
first.taskRunId = undefined;
|
||||
first.sessionStartedAt = first.createdAt - 1;
|
||||
|
||||
const second = replaceRunAfterSteer({
|
||||
previousRunId: "run-legacy-owner-restored",
|
||||
nextRunId: "run-legacy-owner-next",
|
||||
fallback: first,
|
||||
});
|
||||
const second = expectDefined(
|
||||
replaceRunAfterSteer({
|
||||
previousRunId: "run-legacy-owner-restored",
|
||||
nextRunId: "run-legacy-owner-next",
|
||||
fallback: first,
|
||||
}),
|
||||
'replaceRunAfterSteer({ previousRunId: "run-legacy-owner-restored", ne... test invariant',
|
||||
);
|
||||
expect(second.taskRunId).toBeUndefined();
|
||||
expect(second.generation).toBe(3);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import type {
|
||||
@@ -3935,7 +3936,10 @@ describe("subagent registry seam flow", () => {
|
||||
string,
|
||||
SessionEntry
|
||||
>;
|
||||
const current = store[scope.sessionKey];
|
||||
const current = expectDefined(
|
||||
store[scope.sessionKey],
|
||||
"store[scope.sessionKey] test invariant",
|
||||
);
|
||||
const patch = await update(current, { existingEntry: { ...current } });
|
||||
if (patch) {
|
||||
mocks.updateSessionStore(scope.storePath, () => {});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -405,7 +406,10 @@ describe("sessions_spawn context modes", () => {
|
||||
expect(ensureContextEnginesInitializedMock).toHaveBeenCalledTimes(1);
|
||||
expect(resolveContextEngineMock).toHaveBeenCalledTimes(1);
|
||||
expect(ensureContextEnginesInitializedMock.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
resolveContextEngineMock.mock.invocationCallOrder[0],
|
||||
expectDefined(
|
||||
resolveContextEngineMock.mock.invocationCallOrder[0],
|
||||
"resolveContextEngineMock.mock.invocationCallOrder[0] test invariant",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// System prompt stability tests cover deterministic workspace bootstrap file
|
||||
// loading so prompt-cache inputs stay byte-stable.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, beforeEach } from "vitest";
|
||||
import { makeTempWorkspace, writeWorkspaceFile } from "../test-helpers/workspace.js";
|
||||
import {
|
||||
@@ -98,7 +100,7 @@ describe("system prompt stability for cache hits", () => {
|
||||
// All results should have the same file order
|
||||
for (let i = 1; i < results.length; i++) {
|
||||
const names1 = results[0].map((f) => f.name);
|
||||
const namesI = results[i].map((f) => f.name);
|
||||
const namesI = expectDefined(results[i], "results[i] test invariant").map((f) => f.name);
|
||||
expect(namesI).toEqual(names1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tool image tests cover image payload sanitization before tool outputs are
|
||||
// returned to model-visible content blocks.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createNoisyPngBuffer,
|
||||
@@ -61,7 +63,9 @@ describe("tool image sanitizing", () => {
|
||||
});
|
||||
expect(dropped).toBe(0);
|
||||
expect(out.length).toBe(1);
|
||||
const meta = await getImageMetadata(Buffer.from(out[0].data, "base64"));
|
||||
const meta = await getImageMetadata(
|
||||
Buffer.from(expectDefined(out[0], "out[0] test invariant").data, "base64"),
|
||||
);
|
||||
expect(meta?.width).toBeLessThanOrEqual(120);
|
||||
expect(meta?.height).toBeLessThanOrEqual(120);
|
||||
}, 20_000);
|
||||
@@ -158,7 +162,7 @@ describe("tool image sanitizing", () => {
|
||||
];
|
||||
const out = await sanitizeContentBlocksImages(blocks, "browser:screenshot");
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].type).toBe("text");
|
||||
expect(expectDefined(out[0], "out[0] test invariant").type).toBe("text");
|
||||
expect((out[0] as { type: "text"; text: string }).text).toContain("missing data or mimeType");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Live tool replay repair tests validate repaired historical transcripts across
|
||||
// selected real model providers.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import type { Context, Model } from "openclaw/plugin-sdk/llm";
|
||||
@@ -300,7 +302,7 @@ describeLive("tool replay repair live", () => {
|
||||
"toolResult",
|
||||
"user",
|
||||
]);
|
||||
const assistantMessage = sanitized[1];
|
||||
const assistantMessage = expectDefined(sanitized[1], "sanitized[1] test invariant");
|
||||
expect(assistantMessage?.role).toBe("assistant");
|
||||
expect(
|
||||
sanitized.slice(2, 5).map((message) => (message as { toolCallId?: string }).toolCallId),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tool search tests cover catalog compaction, scoped tool lookup, raw fallback
|
||||
// tools, hooks, abort wrapping, and transcript projection.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { setPluginToolMeta } from "../plugins/tools.js";
|
||||
import { wrapToolWithAbortSignal } from "./agent-tools.abort.js";
|
||||
@@ -233,13 +235,16 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: compacted.tools[0] ? {} : undefined,
|
||||
});
|
||||
const result = await runtimeCodeTool.execute("call-1", {
|
||||
code: `
|
||||
const result = await expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-1",
|
||||
{
|
||||
code: `
|
||||
const hits = await openclaw.tools.search("ticket", { limit: 1 });
|
||||
const described = await openclaw.tools.describe(hits[0].id);
|
||||
return await openclaw.tools.call(described.id, { value: "ship" });
|
||||
`,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const alphaCall = mockCall(vi.mocked(alpha.execute));
|
||||
expect(alphaCall[0]).toBe("tool_search_code:call-1:fake_create_ticket:1");
|
||||
@@ -293,7 +298,7 @@ describe("Tool Search", () => {
|
||||
runId: "run-a",
|
||||
config,
|
||||
});
|
||||
const runACallTool = runATools[3];
|
||||
const runACallTool = expectDefined(runATools[3], "runATools[3] test invariant");
|
||||
await runACallTool.execute("call-run-a", {
|
||||
id: "fake_run_a",
|
||||
args: { value: "A" },
|
||||
@@ -342,7 +347,7 @@ describe("Tool Search", () => {
|
||||
catalogRef: localRef,
|
||||
config,
|
||||
});
|
||||
const callTool = tools[3];
|
||||
const callTool = expectDefined(tools[3], "tools[3] test invariant");
|
||||
await callTool.execute("call-local-ref", {
|
||||
id: "fake_local_ref",
|
||||
args: { value: "local" },
|
||||
@@ -1026,7 +1031,7 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: {},
|
||||
});
|
||||
await runtimeCodeTool.execute("call-hooks", {
|
||||
await expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute("call-hooks", {
|
||||
code: `return await openclaw.tools.call("fake_hooked", { value: "ok" });`,
|
||||
});
|
||||
const targetCall = mockCall(vi.mocked(target.execute));
|
||||
@@ -1080,12 +1085,15 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: {},
|
||||
});
|
||||
await runtimeCodeTool.execute("call-repeated", {
|
||||
code: `
|
||||
await expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-repeated",
|
||||
{
|
||||
code: `
|
||||
await openclaw.tools.call("fake_repeated", { value: "one" });
|
||||
return await openclaw.tools.call("fake_repeated", { value: "two" });
|
||||
`,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const firstCall = mockCall(vi.mocked(target.execute));
|
||||
expect(firstCall[0]).toBe("tool_search_code:call-repeated:fake_repeated:1");
|
||||
@@ -1099,9 +1107,12 @@ describe("Tool Search", () => {
|
||||
expect(secondCall[2]).toBeInstanceOf(AbortSignal);
|
||||
expect(secondCall[3]).toBeUndefined();
|
||||
expect(secondCall[4]).toBeUndefined();
|
||||
await runtimeCodeTool.execute("call-repeated-again", {
|
||||
code: `return await openclaw.tools.call("fake_repeated", { value: "three" });`,
|
||||
});
|
||||
await expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-repeated-again",
|
||||
{
|
||||
code: `return await openclaw.tools.call("fake_repeated", { value: "three" });`,
|
||||
},
|
||||
);
|
||||
|
||||
const thirdCall = mockCall(vi.mocked(target.execute), 2);
|
||||
expect(thirdCall[0]).toBe("tool_search_code:call-repeated-again:fake_repeated:1");
|
||||
@@ -1157,8 +1168,8 @@ describe("Tool Search", () => {
|
||||
abortSignal: abortController.signal,
|
||||
executeTool,
|
||||
});
|
||||
const runtimeCodeTool = runtimeTools[0];
|
||||
const runtimeCallTool = runtimeTools[3];
|
||||
const runtimeCodeTool = expectDefined(runtimeTools[0], "runtime code tool");
|
||||
const runtimeCallTool = expectDefined(runtimeTools[3], "runtimeTools[3] test invariant");
|
||||
await runtimeCodeTool.execute(
|
||||
"call-lifecycle",
|
||||
{
|
||||
@@ -1315,12 +1326,15 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: {},
|
||||
});
|
||||
const result = await runtimeCodeTool.execute("call-fire-and-forget", {
|
||||
code: `
|
||||
const result = await expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-fire-and-forget",
|
||||
{
|
||||
code: `
|
||||
openclaw.tools.call("fake_fire_and_forget", { value: "late" });
|
||||
return "done";
|
||||
`,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
expect(target.execute).not.toHaveBeenCalled();
|
||||
const details = resultDetails(result);
|
||||
@@ -1355,7 +1369,7 @@ describe("Tool Search", () => {
|
||||
config: {},
|
||||
});
|
||||
let settled = false;
|
||||
const resultPromise = runtimeCodeTool
|
||||
const resultPromise = expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant")
|
||||
.execute("call-started-bridge", {
|
||||
code: `
|
||||
openclaw.tools.call("fake_then_started", { value: "started" }).then(() => {});
|
||||
@@ -1389,24 +1403,33 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-escape", {
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute("call-escape", {
|
||||
code: `return Function("return process")();`,
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-constructor-escape", {
|
||||
code: `return globalThis.constructor.constructor("return process")();`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-constructor-escape",
|
||||
{
|
||||
code: `return globalThis.constructor.constructor("return process")();`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-console-escape", {
|
||||
code: `return console.log.constructor.constructor("return process")();`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-console-escape",
|
||||
{
|
||||
code: `return console.log.constructor.constructor("return process")();`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-bridge-escape", {
|
||||
code: `return openclaw.tools.call.constructor.constructor("return process")();`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-bridge-escape",
|
||||
{
|
||||
code: `return openclaw.tools.call.constructor.constructor("return process")();`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
@@ -1427,7 +1450,7 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: { tools: { toolSearch: { mode: "tools" } } } as never,
|
||||
});
|
||||
const runtimeCallTool = runtimeTools[3];
|
||||
const runtimeCallTool = expectDefined(runtimeTools[3], "runtimeTools[3] test invariant");
|
||||
|
||||
await expect(
|
||||
runtimeCallTool.execute("call-guessed-file-write", {
|
||||
@@ -1460,10 +1483,13 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeTools[3].execute("call-duplicate-write", {
|
||||
id: "file_write",
|
||||
args: {},
|
||||
}),
|
||||
expectDefined(runtimeTools[3], "runtimeTools[3] test invariant").execute(
|
||||
"call-duplicate-write",
|
||||
{
|
||||
id: "file_write",
|
||||
args: {},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("Did you mean: openclaw:first-plugin:write, openclaw:second-plugin:write?");
|
||||
});
|
||||
|
||||
@@ -1484,7 +1510,7 @@ describe("Tool Search", () => {
|
||||
sessionKey: "agent:main:main",
|
||||
config: { tools: { toolSearch: { mode: "tools" } } } as never,
|
||||
});
|
||||
const runtimeCallTool = runtimeTools[3];
|
||||
const runtimeCallTool = expectDefined(runtimeTools[3], "runtimeTools[3] test invariant");
|
||||
|
||||
await expect(
|
||||
runtimeCallTool.execute("call-missing-raw-tool", {
|
||||
@@ -1514,9 +1540,12 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-code-guessed-file-write", {
|
||||
code: `return await openclaw.tools.call("file_write", { path: "memory/2026-05-22.md" });`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-code-guessed-file-write",
|
||||
{
|
||||
code: `return await openclaw.tools.call("file_write", { path: "memory/2026-05-22.md" });`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow(
|
||||
"Unknown tool id: file_write. Did you mean: write? Use openclaw.tools.search to find a tool, openclaw.tools.describe to inspect it, then openclaw.tools.call with the exact id or name.",
|
||||
);
|
||||
@@ -1539,9 +1568,12 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-missing-tool", {
|
||||
code: `return await openclaw.tools.call("missing_tool", {});`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-missing-tool",
|
||||
{
|
||||
code: `return await openclaw.tools.call("missing_tool", {});`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow(
|
||||
"Unknown tool id: missing_tool. Use openclaw.tools.search to find a tool, openclaw.tools.describe to inspect it, then openclaw.tools.call with the exact id or name.",
|
||||
);
|
||||
@@ -1565,12 +1597,15 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-bridge-result-escape", {
|
||||
code: `
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-bridge-result-escape",
|
||||
{
|
||||
code: `
|
||||
const hits = await openclaw.tools.search("bridge result", { limit: 1 });
|
||||
return hits.constructor.constructor("return process")();
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
).rejects.toThrow();
|
||||
expect(target.execute).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -1593,8 +1628,10 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-controller-escape", {
|
||||
code: `
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-controller-escape",
|
||||
{
|
||||
code: `
|
||||
})(openclaw, console),
|
||||
bridgeMessages.push({
|
||||
id: "forged",
|
||||
@@ -1604,7 +1641,8 @@ describe("Tool Search", () => {
|
||||
(async (openclaw, console) => {
|
||||
return "done";
|
||||
`,
|
||||
}),
|
||||
},
|
||||
),
|
||||
).rejects.toThrow();
|
||||
expect(target.execute).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -1634,7 +1672,7 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-timeout", {
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute("call-timeout", {
|
||||
code: `
|
||||
await openclaw.tools.search("timeout", { limit: 1 });
|
||||
while (true) {}
|
||||
@@ -1694,9 +1732,12 @@ describe("Tool Search", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
runtimeCodeTool.execute("call-abort-timeout", {
|
||||
code: `return await openclaw.tools.call("fake_abort_on_timeout", { value: "wait" });`,
|
||||
}),
|
||||
expectDefined(runtimeCodeTool, "runtimeCodeTool test invariant").execute(
|
||||
"call-abort-timeout",
|
||||
{
|
||||
code: `return await openclaw.tools.call("fake_abort_on_timeout", { value: "wait" });`,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("tool_search_code timed out");
|
||||
if (!observedSignal) {
|
||||
throw new Error("Expected observed abort signal");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Gateway call helper tests pin URL override, token, and RPC scope behavior for
|
||||
// agent tools that route through the local gateway client.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { verifyAgentRuntimeIdentityToken } from "../../gateway/agent-runtime-identity-token.js";
|
||||
import type { CallGatewayOptions } from "../../gateway/call.js";
|
||||
@@ -269,9 +270,12 @@ describe("gateway tool defaults", () => {
|
||||
);
|
||||
|
||||
expect(mocks.callGateway).toHaveBeenCalledTimes(1);
|
||||
const [[callParams]] = mocks.callGateway.mock.calls as unknown as Array<
|
||||
[{ method?: string; scopes?: string[] }]
|
||||
>;
|
||||
const [callParams] = expectDefined(
|
||||
(
|
||||
mocks.callGateway.mock.calls as unknown as Array<[{ method?: string; scopes?: string[] }]>
|
||||
)[0],
|
||||
"(mocks.callGateway.mock.calls as unknown as Array<[{ method?: string; scopes?: string[] }]>)[0] test invariant",
|
||||
);
|
||||
expect(callParams.method).toBe("plugins.sessionAction");
|
||||
expect(callParams.scopes).toEqual(["operator.approvals"]);
|
||||
});
|
||||
@@ -290,9 +294,12 @@ describe("gateway tool defaults", () => {
|
||||
);
|
||||
|
||||
expect(mocks.callGateway).toHaveBeenCalledTimes(1);
|
||||
const [[callParams]] = mocks.callGateway.mock.calls as unknown as Array<
|
||||
[{ method?: string; scopes?: string[] }]
|
||||
>;
|
||||
const [callParams] = expectDefined(
|
||||
(
|
||||
mocks.callGateway.mock.calls as unknown as Array<[{ method?: string; scopes?: string[] }]>
|
||||
)[0],
|
||||
"(mocks.callGateway.mock.calls as unknown as Array<[{ method?: string; scopes?: string[] }]>)[0] test invariant",
|
||||
);
|
||||
expect(callParams.method).toBe("plugins.sessionAction");
|
||||
expect(callParams.scopes).toEqual([
|
||||
"operator.admin",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { ModelApi } from "../../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
@@ -91,7 +92,7 @@ function readJpegDimensions(buffer: Buffer): { width: number; height: number } {
|
||||
offset += 1;
|
||||
continue;
|
||||
}
|
||||
const marker = buffer[offset + 1];
|
||||
const marker = expectDefined(buffer[offset + 1], "buffer[offset + 1] test invariant");
|
||||
offset += 2;
|
||||
if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7)) {
|
||||
continue;
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { isInboundPathAllowed } from "@openclaw/media-core/inbound-path-policy";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { ModelDefinitionConfig } from "../../config/types.models.js";
|
||||
@@ -302,7 +303,7 @@ function readJpegDimensions(buffer: Buffer): { width: number; height: number } {
|
||||
offset += 1;
|
||||
continue;
|
||||
}
|
||||
const marker = buffer[offset + 1];
|
||||
const marker = expectDefined(buffer[offset + 1], "buffer[offset + 1] test invariant");
|
||||
offset += 2;
|
||||
if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7)) {
|
||||
continue;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import type { callGateway as gatewayCall } from "../../gateway/call.js";
|
||||
import { deleteTestEnvValue, setTestEnvValue } from "../../test-utils/env.js";
|
||||
@@ -150,7 +151,10 @@ describe("sessions_history redaction", () => {
|
||||
method: "chat.history",
|
||||
params: { sessionKey: "main", limit: 2 },
|
||||
});
|
||||
expect((requests[0].params as Record<string, unknown>).offset).toBeUndefined();
|
||||
expect(
|
||||
(expectDefined(requests[0], "requests[0] test invariant").params as Record<string, unknown>)
|
||||
.offset,
|
||||
).toBeUndefined();
|
||||
expect((result.details as Record<string, unknown>).offset).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// announce-target resolution, and assistant-visible text sanitization.
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ChannelMessagingAdapter } from "../../channels/plugins/types.js";
|
||||
@@ -1033,8 +1034,12 @@ describe("sessions_send gating", () => {
|
||||
if (kind !== "group") {
|
||||
return null;
|
||||
}
|
||||
const [id, threadId] = rawId.split(":topic:");
|
||||
return threadId ? { id, threadId, baseConversationId: id } : null;
|
||||
const [rawConversationId, threadId] = rawId.split(":topic:");
|
||||
if (!threadId) {
|
||||
return null;
|
||||
}
|
||||
const id = expectDefined(rawConversationId, "Telegram conversation id");
|
||||
return { id, threadId, baseConversationId: id };
|
||||
});
|
||||
setRuntimeConfigSnapshot({ plugins: { entries: { telegram: { enabled: true } } } });
|
||||
expect(parseSessionThreadInfo(topicSessionKey).threadId).toBe("77");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// TTS tool tests cover guidance, speech runtime arguments, delivery metadata,
|
||||
// timeout validation, and reply-directive defusing.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as ttsRuntime from "../../tts/tts.js";
|
||||
import { createTtsTool } from "./tts-tool.js";
|
||||
@@ -171,7 +173,10 @@ describe("createTtsTool", () => {
|
||||
const tool = createTtsTool();
|
||||
const result = await tool.execute("call-1", { text: spoken });
|
||||
|
||||
const rendered = (result.content as Array<{ type: string; text: string }>)[0].text;
|
||||
const rendered = expectDefined(
|
||||
(result.content as Array<{ type: string; text: string }>)[0],
|
||||
"(result.content as Array<{ type: string; text: string }>)[0] test invariant",
|
||||
).text;
|
||||
// The literal directive tokens must not appear verbatim, so
|
||||
// parseReplyDirectives can no longer surface them as media/audio flags.
|
||||
expect(rendered).not.toMatch(/^MEDIA:/m);
|
||||
@@ -194,7 +199,10 @@ describe("createTtsTool", () => {
|
||||
const tool = createTtsTool();
|
||||
const result = await tool.execute("call-1", { text: spoken });
|
||||
|
||||
const rendered = (result.content as Array<{ type: string; text: string }>)[0].text;
|
||||
const rendered = expectDefined(
|
||||
(result.content as Array<{ type: string; text: string }>)[0],
|
||||
"(result.content as Array<{ type: string; text: string }>)[0] test invariant",
|
||||
).text;
|
||||
expect(rendered).toContain("\u00A0\u2060MEDIA:/tmp/secret.png");
|
||||
expect(rendered).not.toMatch(/^\u00A0MEDIA:/m);
|
||||
});
|
||||
@@ -211,7 +219,10 @@ describe("createTtsTool", () => {
|
||||
const tool = createTtsTool();
|
||||
const result = await tool.execute("call-1", { text: spoken });
|
||||
|
||||
const rendered = (result.content as Array<{ type: string; text: string }>)[0].text;
|
||||
const rendered = expectDefined(
|
||||
(result.content as Array<{ type: string; text: string }>)[0],
|
||||
"(result.content as Array<{ type: string; text: string }>)[0] test invariant",
|
||||
).text;
|
||||
expect(rendered).not.toMatch(/^[ \t]*```/m);
|
||||
expect(rendered).toContain("`\u2060``");
|
||||
expect(rendered).toContain("\u2060MEDIA:");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// web_fetch SSRF tests cover URL, DNS, redirect, and proxy policy enforcement
|
||||
// before network requests reach fetch or provider fallbacks.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as ssrf from "../../infra/net/ssrf.js";
|
||||
import { type FetchMock, withFetchPreconnect } from "../../test-utils/fetch-mock.js";
|
||||
@@ -44,7 +45,10 @@ function expectRawFetchSuccessDetails(details: unknown) {
|
||||
|
||||
function firstFetchUrl(fetchSpy: ReturnType<typeof setMockFetch>): string {
|
||||
const input = fetchSpy.mock.calls[0]?.[0];
|
||||
return input instanceof Request ? input.url : input instanceof URL ? input.href : input;
|
||||
return expectDefined(
|
||||
input instanceof Request ? input.url : input instanceof URL ? input.href : input,
|
||||
"input instanceof Request ? input.url : input instanceof URL ? input.h... test invariant",
|
||||
);
|
||||
}
|
||||
|
||||
function createWebFetchToolForTest(params?: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Transcript redaction tests cover structured and text transcript fields so
|
||||
// secrets do not persist in logs or replay artifacts.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
@@ -74,7 +76,10 @@ describe("redactTranscriptMessage", () => {
|
||||
it("redacts text block matching default patterns (sk- token)", () => {
|
||||
const msg = textMessage("key is sk-abcdef1234567890xyz end");
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const text = (msgContent(result) as Array<{ text: string }>)[0].text;
|
||||
const text = expectDefined(
|
||||
(msgContent(result) as Array<{ text: string }>)[0],
|
||||
"(msgContent(result) as Array<{ text: string }>)[0] test invariant",
|
||||
).text;
|
||||
expect(text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(text).toContain("end");
|
||||
});
|
||||
@@ -87,7 +92,10 @@ describe("redactTranscriptMessage", () => {
|
||||
],
|
||||
} as unknown as AgentMessage;
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ thinking: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ thinking: string }>)[0],
|
||||
"(msgContent(result) as Array<{ thinking: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.thinking).not.toContain("sk-abcdef1234567890xyz");
|
||||
});
|
||||
|
||||
@@ -139,7 +147,10 @@ describe("redactTranscriptMessage", () => {
|
||||
msg,
|
||||
cfg("tools", ["reasoning-1", "reasoning", "summary_text"]),
|
||||
);
|
||||
const block = (msgContent(result) as Array<{ thinking: string; thinkingSignature: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ thinking: string; thinkingSignature: string }>)[0],
|
||||
"(msgContent(result) as Array<{ thinking: string; thinkingSignature: s... test invariant",
|
||||
);
|
||||
const replayItem = JSON.parse(block.thinkingSignature) as {
|
||||
id: string;
|
||||
type: string;
|
||||
@@ -150,8 +161,10 @@ describe("redactTranscriptMessage", () => {
|
||||
};
|
||||
const blockMetadata = (block as unknown as { openclawReasoningReplay: Record<string, unknown> })
|
||||
.openclawReasoningReplay;
|
||||
const rejectedSignature = (msgContent(result) as Array<{ thinkingSignature: string }>)[1]
|
||||
.thinkingSignature;
|
||||
const rejectedSignature = expectDefined(
|
||||
(msgContent(result) as Array<{ thinkingSignature: string }>)[1],
|
||||
"(msgContent(result) as Array<{ thinkingSignature: string }>)[1] test invariant",
|
||||
).thinkingSignature;
|
||||
expect(block.thinking).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(replayItem.id).toBe("reasoning-1");
|
||||
expect(replayItem.type).toBe("reasoning");
|
||||
@@ -255,8 +268,13 @@ describe("redactTranscriptMessage", () => {
|
||||
SHORT_GOOGLE_THOUGHT_SIGNATURE,
|
||||
]),
|
||||
);
|
||||
const preservedBlock = (msgContent(result) as Array<Record<string, string>>)[0];
|
||||
expect(preservedBlock[signatureKey]).toBe(expectedSignature);
|
||||
const preservedBlock = expectDefined(
|
||||
(msgContent(result) as Array<Record<string, string>>)[0],
|
||||
"(msgContent(result) as Array<Record<string, string>>)[0] test invariant",
|
||||
);
|
||||
expect(
|
||||
expectDefined(preservedBlock[signatureKey], "preservedBlock[signatureKey] test invariant"),
|
||||
).toBe(expectedSignature);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -286,7 +304,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ thoughtSignature: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ thoughtSignature: string }>)[0],
|
||||
"(msgContent(result) as Array<{ thoughtSignature: string }>)[0] test invariant",
|
||||
);
|
||||
expect(JSON.parse(block.thoughtSignature)).toEqual({
|
||||
type: "reasoning.encrypted",
|
||||
data: CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES,
|
||||
@@ -318,7 +339,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ thoughtSignature: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ thoughtSignature: string }>)[0],
|
||||
"(msgContent(result) as Array<{ thoughtSignature: string }>)[0] test invariant",
|
||||
);
|
||||
expect(JSON.parse(block.thoughtSignature)).toEqual({
|
||||
type: "reasoning.encrypted",
|
||||
data: CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES,
|
||||
@@ -358,12 +382,15 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (
|
||||
msgContent(result) as Array<{
|
||||
thoughtSignature: string;
|
||||
arguments: Record<string, string>;
|
||||
}>
|
||||
)[0];
|
||||
const block = expectDefined(
|
||||
(
|
||||
msgContent(result) as Array<{
|
||||
thoughtSignature: string;
|
||||
arguments: Record<string, string>;
|
||||
}>
|
||||
)[0],
|
||||
"( msgContent(result) as Array<{ thoughtSignature: string; arguments: ... test invariant",
|
||||
);
|
||||
expect(block.thoughtSignature).toBe(GOOGLE_THOUGHT_SIGNATURE);
|
||||
expect(JSON.stringify(block.arguments)).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(block.arguments.apiKey).toBe("plains…e123");
|
||||
@@ -395,11 +422,21 @@ describe("redactTranscriptMessage", () => {
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools", [GOOGLE_THOUGHT_SIGNATURE]));
|
||||
const blocks = msgContent(result) as Array<Record<string, string>>;
|
||||
expect(blocks[0].text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(blocks[0].textSignature).toBe(GOOGLE_THOUGHT_SIGNATURE);
|
||||
expect(blocks[1].textSignature).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(blocks[2].thinking).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(blocks[2].thought_signature).toBe(SHORT_GOOGLE_THOUGHT_SIGNATURE);
|
||||
expect(expectDefined(blocks[0], "blocks[0] test invariant").text).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(expectDefined(blocks[0], "blocks[0] test invariant").textSignature).toBe(
|
||||
GOOGLE_THOUGHT_SIGNATURE,
|
||||
);
|
||||
expect(expectDefined(blocks[1], "blocks[1] test invariant").textSignature).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(expectDefined(blocks[2], "blocks[2] test invariant").thinking).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(expectDefined(blocks[2], "blocks[2] test invariant").thought_signature).toBe(
|
||||
SHORT_GOOGLE_THOUGHT_SIGNATURE,
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["openai-responses", "openclaw-openai-responses-transport"])(
|
||||
@@ -414,7 +451,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools", [COPILOT_CONNECTION_BOUND_ID]));
|
||||
const block = (msgContent(result) as Array<{ textSignature: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ textSignature: string }>)[0],
|
||||
"(msgContent(result) as Array<{ textSignature: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.textSignature).toBe(textSignature);
|
||||
},
|
||||
);
|
||||
@@ -444,17 +484,21 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const thinkingBlock = (
|
||||
msgContent(result) as Array<{ thinking: string; thinkingSignature: string }>
|
||||
)[0];
|
||||
const redactedBlock = (
|
||||
msgContent(result) as Array<{
|
||||
data: string;
|
||||
signature: string;
|
||||
thinkingSignature: string;
|
||||
metadata: { accessToken: string };
|
||||
}>
|
||||
)[1];
|
||||
const thinkingBlock = expectDefined(
|
||||
(msgContent(result) as Array<{ thinking: string; thinkingSignature: string }>)[0],
|
||||
"( msgContent(result) as Array<{ thinking: string; thinkingSignature: ... test invariant",
|
||||
);
|
||||
const redactedBlock = expectDefined(
|
||||
(
|
||||
msgContent(result) as Array<{
|
||||
data: string;
|
||||
signature: string;
|
||||
thinkingSignature: string;
|
||||
metadata: { accessToken: string };
|
||||
}>
|
||||
)[1],
|
||||
"( msgContent(result) as Array<{ data: string; signature: string; thin... test invariant",
|
||||
);
|
||||
expect(thinkingBlock.thinking).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(thinkingBlock.thinkingSignature).toBe(CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES);
|
||||
expect(redactedBlock.data).toBe(CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES);
|
||||
@@ -562,37 +606,59 @@ describe("redactTranscriptMessage", () => {
|
||||
const googleBlocks = msgContent(redactTranscriptMessage(googleMsg, cfg("tools"))) as Array<
|
||||
Record<string, string>
|
||||
>;
|
||||
expect(googleBlocks[0].thoughtSignature).toBe(GOOGLE_CREDENTIAL_COLLISION);
|
||||
expect(googleBlocks[1].thinkingSignature).toBe(ALIBABA_CREDENTIAL_COLLISION);
|
||||
expect(expectDefined(googleBlocks[0], "googleBlocks[0] test invariant").thoughtSignature).toBe(
|
||||
GOOGLE_CREDENTIAL_COLLISION,
|
||||
);
|
||||
expect(expectDefined(googleBlocks[1], "googleBlocks[1] test invariant").thinkingSignature).toBe(
|
||||
ALIBABA_CREDENTIAL_COLLISION,
|
||||
);
|
||||
|
||||
const anthropicBlocks = msgContent(
|
||||
redactTranscriptMessage(anthropicMsg, cfg("tools")),
|
||||
) as Array<Record<string, string>>;
|
||||
expect(anthropicBlocks[0].signature).toBe(OPENAI_COMPAT_OPAQUE_COLLISION);
|
||||
expect(anthropicBlocks[1].data).toBe(githubToken);
|
||||
expect(expectDefined(anthropicBlocks[0], "anthropicBlocks[0] test invariant").signature).toBe(
|
||||
OPENAI_COMPAT_OPAQUE_COLLISION,
|
||||
);
|
||||
expect(expectDefined(anthropicBlocks[1], "anthropicBlocks[1] test invariant").data).toBe(
|
||||
githubToken,
|
||||
);
|
||||
|
||||
const completionsBlocks = msgContent(
|
||||
redactTranscriptMessage(openAICompletionsMsg, cfg("tools")),
|
||||
) as Array<{ thoughtSignature: string }>;
|
||||
expect(JSON.parse(completionsBlocks[0].thoughtSignature)).toEqual({
|
||||
expect(
|
||||
JSON.parse(
|
||||
expectDefined(completionsBlocks[0], "completionsBlocks[0] test invariant").thoughtSignature,
|
||||
),
|
||||
).toEqual({
|
||||
type: "reasoning.encrypted",
|
||||
data: githubToken,
|
||||
id: "reasoning-encrypted-1",
|
||||
});
|
||||
expect(completionsBlocks[1].thoughtSignature).not.toBe(githubToken);
|
||||
expect(
|
||||
expectDefined(completionsBlocks[1], "completionsBlocks[1] test invariant").thoughtSignature,
|
||||
).not.toBe(githubToken);
|
||||
|
||||
const googleCompletionsBlock = (
|
||||
msgContent(redactTranscriptMessage(googleOpenAICompletionsMsg, googleCompatCfg())) as Array<{
|
||||
thoughtSignature: string;
|
||||
}>
|
||||
)[0];
|
||||
const googleCompletionsBlock = expectDefined(
|
||||
(
|
||||
msgContent(
|
||||
redactTranscriptMessage(googleOpenAICompletionsMsg, googleCompatCfg()),
|
||||
) as Array<{
|
||||
thoughtSignature: string;
|
||||
}>
|
||||
)[0],
|
||||
"( msgContent(redactTranscriptMessage(googleOpenAICompletionsMsg, goog... test invariant",
|
||||
);
|
||||
expect(googleCompletionsBlock.thoughtSignature).toBe(OPENAI_COMPAT_OPAQUE_COLLISION);
|
||||
|
||||
const responsesBlock = (
|
||||
msgContent(redactTranscriptMessage(openAIResponsesMsg, cfg("tools"))) as Array<{
|
||||
thinkingSignature: string;
|
||||
}>
|
||||
)[0];
|
||||
const responsesBlock = expectDefined(
|
||||
(
|
||||
msgContent(redactTranscriptMessage(openAIResponsesMsg, cfg("tools"))) as Array<{
|
||||
thinkingSignature: string;
|
||||
}>
|
||||
)[0],
|
||||
'( msgContent(redactTranscriptMessage(openAIResponsesMsg, cfg("tools")... test invariant',
|
||||
);
|
||||
expect(JSON.parse(responsesBlock.thinkingSignature)).toEqual({
|
||||
id: "reasoning-1",
|
||||
type: "reasoning",
|
||||
@@ -748,10 +814,17 @@ describe("redactTranscriptMessage", () => {
|
||||
cfg("tools", [CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES, SHORT_GOOGLE_THOUGHT_SIGNATURE]),
|
||||
);
|
||||
const blocks = msgContent(result) as Array<Record<string, string>>;
|
||||
expect(JSON.parse(blocks[0].thinkingSignature).encrypted_content).toBe(
|
||||
CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES,
|
||||
expect(
|
||||
JSON.parse(
|
||||
expectDefined(
|
||||
expectDefined(blocks[0], "thinking block").thinkingSignature,
|
||||
"thinking signature",
|
||||
),
|
||||
).encrypted_content,
|
||||
).toBe(CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES);
|
||||
expect(expectDefined(blocks[1], "blocks[1] test invariant").thoughtSignature).toBe(
|
||||
SHORT_GOOGLE_THOUGHT_SIGNATURE,
|
||||
);
|
||||
expect(blocks[1].thoughtSignature).toBe(SHORT_GOOGLE_THOUGHT_SIGNATURE);
|
||||
});
|
||||
|
||||
it("redacts provider-shaped fields outside direct assistant content blocks", () => {
|
||||
@@ -784,7 +857,10 @@ describe("redactTranscriptMessage", () => {
|
||||
content: [{ type: "toolCallDelta", partialJson: '{"key":"sk-abcdef1234567890xyz"}' }],
|
||||
} as unknown as AgentMessage;
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ partialJson: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ partialJson: string }>)[0],
|
||||
"(msgContent(result) as Array<{ partialJson: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.partialJson).not.toContain("sk-abcdef1234567890xyz");
|
||||
});
|
||||
|
||||
@@ -806,7 +882,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ arguments: unknown }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ arguments: unknown }>)[0],
|
||||
"(msgContent(result) as Array<{ arguments: unknown }>)[0] test invariant",
|
||||
);
|
||||
const argumentsValue = block.arguments as {
|
||||
command: string;
|
||||
env: { nested: string[] };
|
||||
@@ -819,7 +898,10 @@ describe("redactTranscriptMessage", () => {
|
||||
expect(argumentsValue.count).toBe(1);
|
||||
expect(serializedArguments).toContain("openclaw health");
|
||||
expect(block.arguments).not.toBe(
|
||||
(msgContent(msg) as Array<{ arguments: unknown }>)[0].arguments,
|
||||
expectDefined(
|
||||
(msgContent(msg) as Array<{ arguments: unknown }>)[0],
|
||||
"(msgContent(msg) as Array<{ arguments: unknown }>)[0] test invariant",
|
||||
).arguments,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -842,7 +924,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ arguments: unknown }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ arguments: unknown }>)[0],
|
||||
"(msgContent(result) as Array<{ arguments: unknown }>)[0] test invariant",
|
||||
);
|
||||
const argumentsValue = block.arguments as {
|
||||
apiKey: string;
|
||||
password: string;
|
||||
@@ -878,7 +963,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ input: unknown }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ input: unknown }>)[0],
|
||||
"(msgContent(result) as Array<{ input: unknown }>)[0] test invariant",
|
||||
);
|
||||
const inputValue = block.input as {
|
||||
apiKey: string;
|
||||
nested: { accessToken: string[] };
|
||||
@@ -912,7 +1000,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ input: unknown }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ input: unknown }>)[0],
|
||||
"(msgContent(result) as Array<{ input: unknown }>)[0] test invariant",
|
||||
);
|
||||
const inputValue = block.input as {
|
||||
password: string;
|
||||
nested: { accessToken: string[] };
|
||||
@@ -1004,7 +1095,9 @@ describe("redactTranscriptMessage", () => {
|
||||
nested: { accessToken: string[] };
|
||||
safe: string;
|
||||
};
|
||||
expect(result.content[0].text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(expectDefined(result.content[0], "result.content[0] test invariant").text).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(serializedDetails).not.toContain("plainsecretvalue123");
|
||||
expect(serializedDetails).not.toContain("hunter2");
|
||||
expect(serializedDetails).not.toContain("nestedplainsecret123");
|
||||
@@ -1038,8 +1131,12 @@ describe("redactTranscriptMessage", () => {
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const content = msgContent(result) as Array<{ type: string; text?: string; data?: string }>;
|
||||
expect(content[0].text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(content[1].data).toBe(IMAGE_BASE64_WITH_SECRET_TOKEN_SUBSTRING);
|
||||
expect(expectDefined(content[0], "content[0] test invariant").text).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(expectDefined(content[1], "content[1] test invariant").data).toBe(
|
||||
IMAGE_BASE64_WITH_SECRET_TOKEN_SUBSTRING,
|
||||
);
|
||||
expect(JSON.stringify(result)).not.toContain("sk-abcdef1234567890xyz");
|
||||
});
|
||||
|
||||
@@ -1057,7 +1154,7 @@ describe("redactTranscriptMessage", () => {
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const content = msgContent(result) as Array<{ data: string }>;
|
||||
expect(content[0].data).toBe("sk-abc…0xyz");
|
||||
expect(expectDefined(content[0], "content[0] test invariant").data).toBe("sk-abc…0xyz");
|
||||
});
|
||||
|
||||
it("preserves valid BMP image base64 while redacting adjacent text", () => {
|
||||
@@ -1075,8 +1172,12 @@ describe("redactTranscriptMessage", () => {
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const content = msgContent(result) as Array<{ type: string; text?: string; data?: string }>;
|
||||
expect(content[0].text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(content[1].data).toBe(BMP_BASE64_WITH_SECRET_TOKEN_SUBSTRING);
|
||||
expect(expectDefined(content[0], "content[0] test invariant").text).not.toContain(
|
||||
"sk-abcdef1234567890xyz",
|
||||
);
|
||||
expect(expectDefined(content[1], "content[1] test invariant").data).toBe(
|
||||
BMP_BASE64_WITH_SECRET_TOKEN_SUBSTRING,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves provider-style image base64 source data", () => {
|
||||
@@ -1096,7 +1197,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ source: { data: string }; apiKey: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ source: { data: string }; apiKey: string }>)[0],
|
||||
"(msgContent(result) as Array<{ source: { data: string }; apiKey: stri... test invariant",
|
||||
);
|
||||
expect(block.source.data).toBe(IMAGE_BASE64_WITH_SECRET_TOKEN_SUBSTRING);
|
||||
expect(block.apiKey).toBe("plains…e123");
|
||||
});
|
||||
@@ -1117,9 +1221,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (
|
||||
msgContent(result) as Array<{ source: { data: string; media_type: string } }>
|
||||
)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ source: { data: string; media_type: string } }>)[0],
|
||||
"( msgContent(result) as Array<{ source: { data: string; media_type: s... test invariant",
|
||||
);
|
||||
expect(block.source.data).toBe(IMAGE_BASE64_WITH_SECRET_TOKEN_SUBSTRING);
|
||||
expect(block.source.media_type).toBe("image/png");
|
||||
});
|
||||
@@ -1138,7 +1243,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ image_url: string; data: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ image_url: string; data: string }>)[0],
|
||||
"(msgContent(result) as Array<{ image_url: string; data: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.image_url).toBe(dataUrl);
|
||||
expect(block.data).toBe("AKIDAB…MNOP");
|
||||
});
|
||||
@@ -1156,7 +1264,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ image_url: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ image_url: string }>)[0],
|
||||
"(msgContent(result) as Array<{ image_url: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.image_url).toBe(dataUrl);
|
||||
});
|
||||
|
||||
@@ -1174,7 +1285,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ image_url: string }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ image_url: string }>)[0],
|
||||
"(msgContent(result) as Array<{ image_url: string }>)[0] test invariant",
|
||||
);
|
||||
expect(block.image_url).toBe(canonicalDataUrl);
|
||||
});
|
||||
|
||||
@@ -1191,7 +1305,10 @@ describe("redactTranscriptMessage", () => {
|
||||
} as unknown as AgentMessage;
|
||||
|
||||
const result = redactTranscriptMessage(msg, cfg("tools"));
|
||||
const block = (msgContent(result) as Array<{ image_url: { url: string } }>)[0];
|
||||
const block = expectDefined(
|
||||
(msgContent(result) as Array<{ image_url: { url: string } }>)[0],
|
||||
"(msgContent(result) as Array<{ image_url: { url: string } }>)[0] test invariant",
|
||||
);
|
||||
expect(block.image_url.url).toBe(dataUrl);
|
||||
});
|
||||
|
||||
@@ -1240,7 +1357,10 @@ describe("redactTranscriptMessage", () => {
|
||||
it("redacts using custom pattern without dropping default patterns", () => {
|
||||
const msg = textMessage("email peter@dc.io and key sk-abcdef1234567890xyz ok");
|
||||
const result = redactTranscriptMessage(msg, cfg("tools", [EMAIL_PATTERN]));
|
||||
const text = (msgContent(result) as Array<{ text: string }>)[0].text;
|
||||
const text = expectDefined(
|
||||
(msgContent(result) as Array<{ text: string }>)[0],
|
||||
"(msgContent(result) as Array<{ text: string }>)[0] test invariant",
|
||||
).text;
|
||||
expect(text).not.toContain("peter@dc.io");
|
||||
expect(text).not.toContain("sk-abcdef1234567890xyz");
|
||||
expect(text).toContain("ok");
|
||||
@@ -1322,7 +1442,10 @@ describe("redactTranscriptMessage", () => {
|
||||
it("redacts with cfg=undefined (falls back to default patterns)", () => {
|
||||
const msg = textMessage("key is sk-abcdef1234567890xyz");
|
||||
const result = redactTranscriptMessage(msg, undefined);
|
||||
const text = (msgContent(result) as Array<{ text: string }>)[0].text;
|
||||
const text = expectDefined(
|
||||
(msgContent(result) as Array<{ text: string }>)[0],
|
||||
"(msgContent(result) as Array<{ text: string }>)[0] test invariant",
|
||||
).text;
|
||||
expect(text).not.toContain("sk-abcdef1234567890xyz");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Tests text chunking helpers used by auto-reply delivery. */
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import * as fences from "../../packages/markdown-core/src/fences.js";
|
||||
import { hasBalancedFences } from "../test-utils/chunk-test-helpers.js";
|
||||
@@ -223,7 +225,9 @@ describe("chunkText", () => {
|
||||
expectChunkTextCase({ text, limit, assert });
|
||||
});
|
||||
|
||||
runChunkCases(chunkText, [parentheticalCases[0]]);
|
||||
runChunkCases(chunkText, [
|
||||
expectDefined(parentheticalCases[0], "parentheticalCases[0] test invariant"),
|
||||
]);
|
||||
});
|
||||
|
||||
describe("resolveTextChunkLimit", () => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import fs from "node:fs/promises";
|
||||
import { basename, join } from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { slugifySessionKey } from "../agents/sandbox/shared.js";
|
||||
import { CONFIG_DIR } from "../utils.js";
|
||||
@@ -171,7 +172,10 @@ describe("stageSandboxMedia scp remote paths", () => {
|
||||
sessionCtx.MediaPaths = [remotePath];
|
||||
childProcessMocks.spawn.mockImplementation((_command, argsUnknown) => {
|
||||
const args = argsUnknown as string[];
|
||||
const localPath = args[args.length - 1];
|
||||
const localPath = expectDefined(
|
||||
args[args.length - 1],
|
||||
"args[args.length - 1] test invariant",
|
||||
);
|
||||
const child = new EventEmitter() as EventEmitter & {
|
||||
stderr: EventEmitter & { setEncoding: (_encoding: string) => void };
|
||||
};
|
||||
@@ -230,7 +234,10 @@ describe("stageSandboxMedia scp remote paths", () => {
|
||||
sessionCtx.MediaPaths = [remotePath];
|
||||
childProcessMocks.spawn.mockImplementation((_command, argsUnknown) => {
|
||||
const args = argsUnknown as string[];
|
||||
const localPath = args[args.length - 1];
|
||||
const localPath = expectDefined(
|
||||
args[args.length - 1],
|
||||
"args[args.length - 1] test invariant",
|
||||
);
|
||||
const child = new EventEmitter() as EventEmitter & {
|
||||
stderr: EventEmitter & { setEncoding: (_encoding: string) => void };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Tests abort request handling, cutoff persistence, and active run cleanup.
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SubagentRunRecord } from "../../agents/subagent-registry.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
@@ -1381,8 +1382,14 @@ describe("abort detection", () => {
|
||||
expect(result.stoppedSubagents).toBe(1);
|
||||
expectSessionLaneCleared(depth2Key);
|
||||
expect(subagentRegistryMocks.markSubagentRunTerminated).toHaveBeenCalledTimes(1);
|
||||
const [[terminatedRun]] = subagentRegistryMocks.markSubagentRunTerminated.mock
|
||||
.calls as unknown as Array<[{ runId?: string; childSessionKey?: string }]>;
|
||||
const [terminatedRun] = expectDefined(
|
||||
(
|
||||
subagentRegistryMocks.markSubagentRunTerminated.mock.calls as unknown as Array<
|
||||
[{ runId?: string; childSessionKey?: string }]
|
||||
>
|
||||
)[0],
|
||||
"(subagentRegistryMocks.markSubagentRunTerminated.mock.calls as unknown as Array<\n [{ runId?: string; childSessionKey?: string }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(terminatedRun.runId).toBe("run-2");
|
||||
expect(terminatedRun.childSessionKey).toBe(depth2Key);
|
||||
});
|
||||
@@ -1481,8 +1488,14 @@ describe("abort detection", () => {
|
||||
expect(result.stoppedSubagents).toBe(1);
|
||||
expectSessionLaneCleared(depth2Key);
|
||||
expect(subagentRegistryMocks.markSubagentRunTerminated).toHaveBeenCalledTimes(1);
|
||||
const [[terminatedRun]] = subagentRegistryMocks.markSubagentRunTerminated.mock
|
||||
.calls as unknown as Array<[{ runId?: string; childSessionKey?: string }]>;
|
||||
const [terminatedRun] = expectDefined(
|
||||
(
|
||||
subagentRegistryMocks.markSubagentRunTerminated.mock.calls as unknown as Array<
|
||||
[{ runId?: string; childSessionKey?: string }]
|
||||
>
|
||||
)[0],
|
||||
"(subagentRegistryMocks.markSubagentRunTerminated.mock.calls as unknown as Array<\n [{ runId?: string; childSessionKey?: string }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(terminatedRun.runId).toBe("run-active-child");
|
||||
expect(terminatedRun.childSessionKey).toBe(depth2Key);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Tests reply payload construction and metadata propagation from agent runs.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { ChannelThreadingAdapter } from "../../channels/plugins/types.public.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
@@ -167,7 +169,10 @@ describe("buildReplyPayloads media filter integration", () => {
|
||||
originatingChatType: "dm",
|
||||
});
|
||||
|
||||
expect(getReplyPayloadMetadata(replyPayloads[0])?.replyDelivery).toEqual({
|
||||
expect(
|
||||
getReplyPayloadMetadata(expectDefined(replyPayloads[0], "replyPayloads[0] test invariant"))
|
||||
?.replyDelivery,
|
||||
).toEqual({
|
||||
chatType: "direct",
|
||||
replyToMode: "first",
|
||||
});
|
||||
@@ -210,9 +215,12 @@ describe("buildReplyPayloads media filter integration", () => {
|
||||
text: "⚠️ API rate limit reached.",
|
||||
replyToId: "msg-1",
|
||||
});
|
||||
expectFields(getReplyPayloadMetadata(replyPayloads[0]), {
|
||||
deliverDespiteSourceReplySuppression: true,
|
||||
});
|
||||
expectFields(
|
||||
getReplyPayloadMetadata(expectDefined(replyPayloads[0], "replyPayloads[0] test invariant")),
|
||||
{
|
||||
deliverDespiteSourceReplySuppression: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("sanitizes source reply transcript mirror text with final payload text", async () => {
|
||||
@@ -241,9 +249,10 @@ describe("buildReplyPayloads media filter integration", () => {
|
||||
|
||||
expect(replyPayloads).toHaveLength(1);
|
||||
expect(replyPayloads[0]?.text).toBe("Visible\n\nDone");
|
||||
expect(getReplyPayloadMetadata(replyPayloads[0])?.sourceReplyTranscriptMirror?.text).toBe(
|
||||
"Visible\n\nDone",
|
||||
);
|
||||
expect(
|
||||
getReplyPayloadMetadata(expectDefined(replyPayloads[0], "replyPayloads[0] test invariant"))
|
||||
?.sourceReplyTranscriptMirror?.text,
|
||||
).toBe("Visible\n\nDone");
|
||||
});
|
||||
|
||||
it("strips media URL from payload when in messagingToolSentMediaUrls", async () => {
|
||||
@@ -254,7 +263,9 @@ describe("buildReplyPayloads media filter integration", () => {
|
||||
});
|
||||
|
||||
expect(replyPayloads).toHaveLength(1);
|
||||
expect(replyPayloads[0].mediaUrl).toBeUndefined();
|
||||
expect(
|
||||
expectDefined(replyPayloads[0], "replyPayloads[0] test invariant").mediaUrl,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves media URL when not in messagingToolSentMediaUrls", async () => {
|
||||
@@ -265,7 +276,9 @@ describe("buildReplyPayloads media filter integration", () => {
|
||||
});
|
||||
|
||||
expect(replyPayloads).toHaveLength(1);
|
||||
expect(replyPayloads[0].mediaUrl).toBe("file:///tmp/photo.jpg");
|
||||
expect(expectDefined(replyPayloads[0], "replyPayloads[0] test invariant").mediaUrl).toBe(
|
||||
"file:///tmp/photo.jpg",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes sent media URLs before deduping normalized reply media", async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Tests usage-line formatting for agent runner completion summaries.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyPayloadMetadata, setReplyPayloadMetadata } from "../reply-payload.js";
|
||||
import { appendUsageLine } from "./agent-runner-usage-line.js";
|
||||
@@ -21,13 +22,15 @@ describe("appendUsageLine", () => {
|
||||
const [updated] = appendUsageLine([payload], "Usage: 12 in / 3 out");
|
||||
|
||||
expect(updated).toEqual({ text: "message tool reply\nUsage: 12 in / 3 out" });
|
||||
expect(getReplyPayloadMetadata(updated)).toMatchObject({
|
||||
deliverDespiteSourceReplySuppression: true,
|
||||
sourceReplyTranscriptMirror: {
|
||||
sessionKey: "agent:main:telegram:direct:123",
|
||||
idempotencyKey: "run-1:internal-source-reply:0",
|
||||
text: "message tool reply\nUsage: 12 in / 3 out",
|
||||
expect(getReplyPayloadMetadata(expectDefined(updated, "updated test invariant"))).toMatchObject(
|
||||
{
|
||||
deliverDespiteSourceReplySuppression: true,
|
||||
sourceReplyTranscriptMirror: {
|
||||
sessionKey: "agent:main:telegram:direct:123",
|
||||
idempotencyKey: "run-1:internal-source-reply:0",
|
||||
text: "message tool reply\nUsage: 12 in / 3 out",
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Tests bash command aliases and chat shortcut handling. */
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { handleBashCommand } from "./commands-bash.js";
|
||||
@@ -71,9 +72,14 @@ describe("handleBashCommand alias routing", () => {
|
||||
|
||||
expect(result?.shouldContinue).toBe(false);
|
||||
expect(handleBashChatCommandMock).toHaveBeenCalledTimes(1);
|
||||
const [[bashParams]] = handleBashChatCommandMock.mock.calls as unknown as Array<
|
||||
[{ agentId?: string; sessionKey?: string }]
|
||||
>;
|
||||
const [bashParams] = expectDefined(
|
||||
(
|
||||
handleBashChatCommandMock.mock.calls as unknown as Array<
|
||||
[{ agentId?: string; sessionKey?: string }]
|
||||
>
|
||||
)[0],
|
||||
"(handleBashChatCommandMock.mock.calls as unknown as Array<\n [{ agentId?: string; sessionKey?: string }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(bashParams.agentId).toBe("target");
|
||||
expect(bashParams.sessionKey).toBe("agent:target:whatsapp:direct:test-user");
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Tests session export command packaging, filesystem writes, and prompt bundle capture.
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { HandleCommandsParams } from "./commands-types.js";
|
||||
|
||||
@@ -175,7 +176,11 @@ function sessionDataFromHtml(html: string): Record<string, unknown> {
|
||||
if (!match) {
|
||||
throw new Error("Expected session-data script in exported HTML");
|
||||
}
|
||||
return JSON.parse(Buffer.from(match[1].trim(), "base64").toString("utf-8"));
|
||||
return JSON.parse(
|
||||
Buffer.from(expectDefined(match[1], "match[1] test invariant").trim(), "base64").toString(
|
||||
"utf-8",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe("buildExportSessionReply", () => {
|
||||
@@ -258,8 +263,14 @@ describe("buildExportSessionReply", () => {
|
||||
});
|
||||
|
||||
expect(reply.text).toContain("✅ Session exported!");
|
||||
const [[systemPromptBundleParams]] = hoisted.resolveCommandsSystemPromptBundleMock.mock
|
||||
.calls as unknown as Array<[{ sessionEntry?: { sessionId?: string; updatedAt?: number } }]>;
|
||||
const [systemPromptBundleParams] = expectDefined(
|
||||
(
|
||||
hoisted.resolveCommandsSystemPromptBundleMock.mock.calls as unknown as Array<
|
||||
[{ sessionEntry?: { sessionId?: string; updatedAt?: number } }]
|
||||
>
|
||||
)[0],
|
||||
"(hoisted.resolveCommandsSystemPromptBundleMock.mock.calls as unknown as Array<\n [{ sessionEntry?: { sessionId?: string; updatedAt?: number } }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(systemPromptBundleParams?.sessionEntry?.sessionId).toBe("session-from-store");
|
||||
expect(systemPromptBundleParams?.sessionEntry?.updatedAt).toBe(2);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Tests model command output, catalog loading, and provider auth status rendering.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { testing as cliBackendsTesting } from "../../agents/cli-backends.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
@@ -995,8 +996,14 @@ describe("handleModelsCommand", () => {
|
||||
const result = await handleModelsCommand(params, true);
|
||||
|
||||
expect(result?.reply?.text).toContain("Models (anthropic · 🔑 target-auth) — showing 1-2 of 2");
|
||||
const [[authLabelParams]] = modelAuthLabelMocks.resolveModelAuthLabel.mock
|
||||
.calls as unknown as Array<[{ provider?: string; workspaceDir?: string }]>;
|
||||
const [authLabelParams] = expectDefined(
|
||||
(
|
||||
modelAuthLabelMocks.resolveModelAuthLabel.mock.calls as unknown as Array<
|
||||
[{ provider?: string; workspaceDir?: string }]
|
||||
>
|
||||
)[0],
|
||||
"(modelAuthLabelMocks.resolveModelAuthLabel.mock.calls as unknown as Array<\n [{ provider?: string; workspaceDir?: string }]\n >)[0] test invariant",
|
||||
);
|
||||
expect(authLabelParams.provider).toBe("anthropic");
|
||||
expect(authLabelParams.workspaceDir).toBe("/tmp");
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user