mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-15 19:21:08 +00:00
247 lines
7.9 KiB
TypeScript
247 lines
7.9 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterAll, beforeAll, vi, type Mock } from "vitest";
|
|
import { withFastReplyConfig } from "./reply/get-reply-fast-path.js";
|
|
|
|
export type ReplyRuntimeMocks = {
|
|
runEmbeddedPiAgent: Mock;
|
|
loadModelCatalog: Mock;
|
|
webAuthExists: Mock;
|
|
getWebAuthAgeMs: Mock;
|
|
readWebSelfId: Mock;
|
|
};
|
|
|
|
const replyRuntimeMockState = vi.hoisted(() => ({
|
|
mocks: {
|
|
runEmbeddedPiAgent: vi.fn(),
|
|
loadModelCatalog: vi.fn(),
|
|
webAuthExists: vi.fn().mockResolvedValue(true),
|
|
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
|
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
|
} as ReplyRuntimeMocks,
|
|
}));
|
|
|
|
vi.mock("../agents/pi-embedded.js", () => ({
|
|
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
|
runEmbeddedPiAgent: (...args: unknown[]) =>
|
|
replyRuntimeMockState.mocks.runEmbeddedPiAgent(...args),
|
|
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
|
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
|
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
|
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
|
}));
|
|
|
|
vi.mock("../agents/model-catalog.runtime.js", () => ({
|
|
loadModelCatalog: (...args: unknown[]) => replyRuntimeMockState.mocks.loadModelCatalog(...args),
|
|
}));
|
|
|
|
vi.mock("../agents/auth-profiles/session-override.js", () => ({
|
|
clearSessionAuthProfileOverride: vi.fn(),
|
|
resolveSessionAuthProfileOverride: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
vi.mock("../commands-registry.runtime.js", () => ({
|
|
listChatCommands: () => [],
|
|
}));
|
|
|
|
vi.mock("../skill-commands.runtime.js", () => ({
|
|
listSkillCommandsForWorkspace: () => [],
|
|
}));
|
|
|
|
vi.mock("../plugins/runtime/runtime-web-channel-plugin.js", () => ({
|
|
webAuthExists: (...args: unknown[]) => replyRuntimeMockState.mocks.webAuthExists(...args),
|
|
getWebAuthAgeMs: (...args: unknown[]) => replyRuntimeMockState.mocks.getWebAuthAgeMs(...args),
|
|
readWebSelfId: (...args: unknown[]) => replyRuntimeMockState.mocks.readWebSelfId(...args),
|
|
}));
|
|
|
|
vi.mock("../agents/pi-embedded.runtime.js", () => ({
|
|
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
|
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
|
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
|
resolveActiveEmbeddedRunSessionId: vi.fn().mockReturnValue(undefined),
|
|
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
|
waitForEmbeddedPiRunEnd: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock("./reply/agent-runner.runtime.js", () => ({
|
|
runReplyAgent: async (params: {
|
|
commandBody: string;
|
|
followupRun: {
|
|
prompt: string;
|
|
run: {
|
|
agentDir: string;
|
|
agentId: string;
|
|
config: unknown;
|
|
execOverrides?: unknown;
|
|
inputProvenance?: unknown;
|
|
messageProvider?: string;
|
|
model: string;
|
|
ownerNumbers?: string[];
|
|
provider: string;
|
|
reasoningLevel?: unknown;
|
|
senderIsOwner?: boolean;
|
|
sessionFile: string;
|
|
sessionId: string;
|
|
sessionKey: string;
|
|
skillsSnapshot?: unknown;
|
|
thinkLevel?: unknown;
|
|
timeoutMs?: number;
|
|
verboseLevel?: unknown;
|
|
workspaceDir: string;
|
|
bashElevated?: unknown;
|
|
};
|
|
};
|
|
}) => {
|
|
const result = await replyRuntimeMockState.mocks.runEmbeddedPiAgent({
|
|
prompt: params.followupRun.prompt || params.commandBody,
|
|
agentDir: params.followupRun.run.agentDir,
|
|
agentId: params.followupRun.run.agentId,
|
|
config: params.followupRun.run.config,
|
|
execOverrides: params.followupRun.run.execOverrides,
|
|
inputProvenance: params.followupRun.run.inputProvenance,
|
|
messageProvider: params.followupRun.run.messageProvider,
|
|
model: params.followupRun.run.model,
|
|
ownerNumbers: params.followupRun.run.ownerNumbers,
|
|
provider: params.followupRun.run.provider,
|
|
reasoningLevel: params.followupRun.run.reasoningLevel,
|
|
senderIsOwner: params.followupRun.run.senderIsOwner,
|
|
sessionFile: params.followupRun.run.sessionFile,
|
|
sessionId: params.followupRun.run.sessionId,
|
|
sessionKey: params.followupRun.run.sessionKey,
|
|
skillsSnapshot: params.followupRun.run.skillsSnapshot,
|
|
thinkLevel: params.followupRun.run.thinkLevel,
|
|
timeoutMs: params.followupRun.run.timeoutMs,
|
|
verboseLevel: params.followupRun.run.verboseLevel,
|
|
workspaceDir: params.followupRun.run.workspaceDir,
|
|
bashElevated: params.followupRun.run.bashElevated,
|
|
});
|
|
return result?.payloads?.[0];
|
|
},
|
|
}));
|
|
|
|
type HomeEnvSnapshot = {
|
|
HOME: string | undefined;
|
|
USERPROFILE: string | undefined;
|
|
HOMEDRIVE: string | undefined;
|
|
HOMEPATH: string | undefined;
|
|
OPENCLAW_STATE_DIR: string | undefined;
|
|
OPENCLAW_AGENT_DIR: string | undefined;
|
|
PI_CODING_AGENT_DIR: string | undefined;
|
|
};
|
|
|
|
function snapshotHomeEnv(): HomeEnvSnapshot {
|
|
return {
|
|
HOME: process.env.HOME,
|
|
USERPROFILE: process.env.USERPROFILE,
|
|
HOMEDRIVE: process.env.HOMEDRIVE,
|
|
HOMEPATH: process.env.HOMEPATH,
|
|
OPENCLAW_STATE_DIR: process.env.OPENCLAW_STATE_DIR,
|
|
OPENCLAW_AGENT_DIR: process.env.OPENCLAW_AGENT_DIR,
|
|
PI_CODING_AGENT_DIR: process.env.PI_CODING_AGENT_DIR,
|
|
};
|
|
}
|
|
|
|
function restoreHomeEnv(snapshot: HomeEnvSnapshot) {
|
|
for (const [key, value] of Object.entries(snapshot)) {
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function createTempHomeHarness(options: { prefix: string; beforeEachCase?: () => void }) {
|
|
let fixtureRoot = "";
|
|
let caseId = 0;
|
|
|
|
beforeAll(async () => {
|
|
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), options.prefix));
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (!fixtureRoot) {
|
|
return;
|
|
}
|
|
await fs.rm(fixtureRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
|
const home = path.join(fixtureRoot, `case-${++caseId}`);
|
|
await fs.mkdir(path.join(home, ".openclaw", "agents", "main", "sessions"), { recursive: true });
|
|
const envSnapshot = snapshotHomeEnv();
|
|
process.env.HOME = home;
|
|
process.env.USERPROFILE = home;
|
|
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
|
|
process.env.OPENCLAW_AGENT_DIR = path.join(home, ".openclaw", "agent");
|
|
process.env.PI_CODING_AGENT_DIR = path.join(home, ".openclaw", "agent");
|
|
|
|
if (process.platform === "win32") {
|
|
const match = home.match(/^([A-Za-z]:)(.*)$/);
|
|
if (match) {
|
|
process.env.HOMEDRIVE = match[1];
|
|
process.env.HOMEPATH = match[2] || "\\";
|
|
}
|
|
}
|
|
|
|
try {
|
|
options.beforeEachCase?.();
|
|
return await fn(home);
|
|
} finally {
|
|
restoreHomeEnv(envSnapshot);
|
|
}
|
|
}
|
|
|
|
return { withTempHome };
|
|
}
|
|
|
|
export function makeReplyConfig(home: string) {
|
|
return withFastReplyConfig({
|
|
agents: {
|
|
defaults: {
|
|
model: "anthropic/claude-opus-4-6",
|
|
workspace: path.join(home, "openclaw"),
|
|
},
|
|
},
|
|
channels: {
|
|
whatsapp: {
|
|
allowFrom: ["*"],
|
|
},
|
|
},
|
|
session: { store: path.join(home, "sessions.json") },
|
|
});
|
|
}
|
|
|
|
export function createReplyRuntimeMocks(): ReplyRuntimeMocks {
|
|
return {
|
|
runEmbeddedPiAgent: vi.fn(),
|
|
loadModelCatalog: vi.fn(),
|
|
webAuthExists: vi.fn().mockResolvedValue(true),
|
|
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
|
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
|
};
|
|
}
|
|
|
|
export function installReplyRuntimeMocks(mocks: ReplyRuntimeMocks) {
|
|
replyRuntimeMockState.mocks = mocks;
|
|
}
|
|
|
|
export function resetReplyRuntimeMocks(mocks: ReplyRuntimeMocks) {
|
|
mocks.runEmbeddedPiAgent.mockClear();
|
|
mocks.loadModelCatalog.mockClear();
|
|
mocks.loadModelCatalog.mockResolvedValue([
|
|
{ id: "claude-opus-4-6", name: "Opus 4.5", provider: "anthropic" },
|
|
]);
|
|
}
|
|
|
|
export function makeEmbeddedTextResult(text: string) {
|
|
return {
|
|
payloads: [{ text }],
|
|
meta: {
|
|
durationMs: 5,
|
|
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
|
},
|
|
};
|
|
}
|