perf(test): route more unit tests through fast lane

This commit is contained in:
Peter Steinberger
2026-04-27 17:01:16 +01:00
parent 8ce4f8fc84
commit 1fd0802b88
11 changed files with 271 additions and 243 deletions

View File

@@ -1,127 +1,115 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import { runCrestodian } from "./crestodian.js";
import { createCrestodianTestRuntime } from "./crestodian.test-helpers.js";
import type { CrestodianOverview } from "./overview.js";
vi.mock("./probes.js", () => ({
probeLocalCommand: vi.fn(async (command: string) => ({
command,
found: false,
error: "not found",
})),
probeGatewayUrl: vi.fn(async (url: string) => ({ reachable: false, url, error: "offline" })),
}));
const overview: CrestodianOverview = {
defaultAgentId: "main",
defaultModel: "openai/gpt-5.5",
agents: [{ id: "main", isDefault: true, model: "openai/gpt-5.5" }],
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
tools: {
codex: { command: "codex", found: false, error: "not found" },
claude: { command: "claude", found: false, error: "not found" },
apiKeys: { openai: true, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "local loopback",
reachable: false,
error: "offline",
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
};
vi.mock("./overview.js", () => ({
formatCrestodianOverview: () => "Default model: openai/gpt-5.5",
loadCrestodianOverview: vi.fn(async () => ({
defaultAgentId: "main",
defaultModel: "openai/gpt-5.5",
agents: [{ id: "main", isDefault: true, model: "openai/gpt-5.5" }],
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
tools: {
codex: { command: "codex", found: false, error: "not found" },
claude: { command: "claude", found: false, error: "not found" },
apiKeys: { openai: true, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "local loopback",
reachable: false,
error: "offline",
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
})),
}));
const crestodianOverviewDeps = {
formatOverview: () => "Default model: openai/gpt-5.5",
loadOverview: async () => overview,
};
describe("runCrestodian", () => {
beforeEach(() => {
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("uses the assistant planner only to choose typed operations", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-run-"));
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(tempDir, "openclaw.json"));
const { runtime, lines } = createCrestodianTestRuntime();
const runGatewayRestart = vi.fn(async () => {});
const onReady = vi.fn();
let runGatewayRestartCalls = 0;
let onReadyCalls = 0;
await runCrestodian(
{
message: "the local bridge looks sleepy, poke it",
deps: { runGatewayRestart },
onReady,
deps: {
runGatewayRestart: async () => {
runGatewayRestartCalls += 1;
},
},
onReady: () => {
onReadyCalls += 1;
},
planWithAssistant: async () => ({
reply: "I can queue a Gateway restart.",
command: "restart gateway",
modelLabel: "openai/gpt-5.5",
}),
...crestodianOverviewDeps,
},
runtime,
);
expect(runGatewayRestart).not.toHaveBeenCalled();
expect(onReady).not.toHaveBeenCalled();
expect(runGatewayRestartCalls).toBe(0);
expect(onReadyCalls).toBe(0);
expect(lines.join("\n")).toContain("[crestodian] planner: openai/gpt-5.5");
expect(lines.join("\n")).toContain("[crestodian] interpreted: restart gateway");
expect(lines.join("\n")).toContain("Plan: restart the Gateway. Say yes to apply.");
});
it("keeps deterministic parsing ahead of the assistant planner", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-run-deterministic-"));
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(tempDir, "openclaw.json"));
const { runtime, lines } = createCrestodianTestRuntime();
const planner = vi.fn(async () => ({ command: "restart gateway" }));
const onReady = vi.fn();
let plannerCalls = 0;
let onReadyCalls = 0;
await runCrestodian(
{
message: "models",
planWithAssistant: planner,
onReady,
planWithAssistant: async () => {
plannerCalls += 1;
return { command: "restart gateway" };
},
onReady: () => {
onReadyCalls += 1;
},
...crestodianOverviewDeps,
},
runtime,
);
expect(planner).not.toHaveBeenCalled();
expect(onReady).not.toHaveBeenCalled();
expect(plannerCalls).toBe(0);
expect(onReadyCalls).toBe(0);
expect(lines.join("\n")).toContain("Default model:");
});
it("starts interactive Crestodian in the TUI shell", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-run-tui-"));
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(tempDir, "openclaw.json"));
const { runtime, lines } = createCrestodianTestRuntime();
const runInteractiveTui = vi.fn(async () => {});
const onReady = vi.fn();
let runInteractiveTuiCalls = 0;
let onReadyCalls = 0;
await runCrestodian(
{
input: { isTTY: true } as unknown as NodeJS.ReadableStream,
output: { isTTY: true } as unknown as NodeJS.WritableStream,
runInteractiveTui,
onReady,
runInteractiveTui: async () => {
runInteractiveTuiCalls += 1;
},
onReady: () => {
onReadyCalls += 1;
},
},
runtime,
);
expect(runInteractiveTui).toHaveBeenCalledWith(
expect.objectContaining({ runInteractiveTui }),
runtime,
);
expect(onReady).toHaveBeenCalledTimes(1);
expect(runInteractiveTuiCalls).toBe(1);
expect(onReadyCalls).toBe(1);
expect(lines.join("\n")).not.toContain("Say: status");
});
});

View File

@@ -8,7 +8,11 @@ import {
isPersistentCrestodianOperation,
type CrestodianCommandDeps,
} from "./operations.js";
import { formatCrestodianOverview, loadCrestodianOverview } from "./overview.js";
import {
formatCrestodianOverview,
loadCrestodianOverview,
type CrestodianOverview,
} from "./overview.js";
type CrestodianInteractiveRunner = (
opts: RunCrestodianOptions,
@@ -22,12 +26,27 @@ export type RunCrestodianOptions = {
interactive?: boolean;
onReady?: () => void;
deps?: CrestodianCommandDeps;
formatOverview?: (overview: CrestodianOverview) => string;
loadOverview?: typeof loadCrestodianOverview;
planWithAssistant?: CrestodianAssistantPlanner;
input?: NodeJS.ReadableStream;
output?: NodeJS.WritableStream;
runInteractiveTui?: CrestodianInteractiveRunner;
};
function crestodianCommandDepsFromOptions(
opts: RunCrestodianOptions,
): CrestodianCommandDeps | undefined {
if (!opts.deps && !opts.formatOverview && !opts.loadOverview) {
return undefined;
}
return {
...opts.deps,
...(opts.formatOverview ? { formatOverview: opts.formatOverview } : {}),
...(opts.loadOverview ? { loadOverview: opts.loadOverview } : {}),
};
}
async function runOneShot(
input: string,
runtime: RuntimeEnv,
@@ -36,7 +55,7 @@ async function runOneShot(
const operation = await resolveCrestodianOperation(input, runtime, opts);
await executeCrestodianOperation(operation, runtime, {
approved: opts.yes === true || !isPersistentCrestodianOperation(operation),
deps: opts.deps,
deps: crestodianCommandDepsFromOptions(opts),
});
}
@@ -45,7 +64,7 @@ export async function runCrestodian(
runtime: RuntimeEnv = defaultRuntime,
): Promise<void> {
if (opts.json) {
const overview = await loadCrestodianOverview();
const overview = await (opts.loadOverview ?? loadCrestodianOverview)();
writeRuntimeJson(runtime, overview);
return;
}
@@ -58,9 +77,9 @@ export async function runCrestodian(
delayMs: 0,
fallback: "none",
},
async () => await loadCrestodianOverview(),
async () => await (opts.loadOverview ?? loadCrestodianOverview)(),
);
runtime.log(formatCrestodianOverview(overview));
runtime.log((opts.formatOverview ?? formatCrestodianOverview)(overview));
runtime.log("");
await runOneShot(opts.message, runtime, opts);
return;

View File

@@ -5,9 +5,10 @@ import {
parseCrestodianOperation,
type CrestodianOperation,
} from "./operations.js";
import type { CrestodianOverview } from "./overview.js";
import { loadCrestodianOverview, type CrestodianOverview } from "./overview.js";
export type CrestodianDialogueOptions = {
loadOverview?: typeof loadCrestodianOverview;
planWithAssistant?: CrestodianAssistantPlanner;
};
@@ -28,8 +29,7 @@ export async function resolveCrestodianOperation(
if (!shouldAskAssistant(input, operation)) {
return operation;
}
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await (opts.loadOverview ?? loadCrestodianOverview)();
const planner = opts.planWithAssistant ?? (await import("./assistant.js")).planCrestodianCommand;
const plan = await planner({ input, overview });
if (!plan) {

View File

@@ -10,6 +10,8 @@ import type { CrestodianOverview } from "./overview.js";
type ConfigModule = typeof import("../config/config.js");
type ConfigFileSnapshot = Awaited<ReturnType<ConfigModule["readConfigFileSnapshot"]>>;
type CrestodianOverviewLoader = () => Promise<CrestodianOverview>;
type CrestodianOverviewFormatter = (overview: CrestodianOverview) => string;
export type CrestodianOperation =
| { kind: "none"; message: string }
@@ -47,6 +49,8 @@ export type CrestodianOperationResult = {
};
export type CrestodianCommandDeps = {
formatOverview?: CrestodianOverviewFormatter;
loadOverview?: CrestodianOverviewLoader;
runAgentsAdd?: (
opts: {
name?: string;
@@ -352,6 +356,27 @@ async function readConfigFileSnapshotLazy(): Promise<ConfigFileSnapshot> {
return await readConfigFileSnapshot();
}
async function loadOverviewForOperation(
deps: CrestodianCommandDeps | undefined,
): Promise<CrestodianOverview> {
if (deps?.loadOverview) {
return await deps.loadOverview();
}
const { loadCrestodianOverview } = await import("./overview.js");
return await loadCrestodianOverview();
}
async function formatOverviewForOperation(
overview: CrestodianOverview,
deps: CrestodianCommandDeps | undefined,
): Promise<string> {
if (deps?.formatOverview) {
return deps.formatOverview(overview);
}
const { formatCrestodianOverview } = await import("./overview.js");
return formatCrestodianOverview(overview);
}
async function loadConfigFileMutationHelpers(): Promise<{
mutateConfigFile: ConfigModule["mutateConfigFile"];
readConfigFileSnapshot: ConfigModule["readConfigFileSnapshot"];
@@ -388,9 +413,9 @@ function createNoExitRuntime(runtime: RuntimeEnv): RuntimeEnv {
async function resolveTuiAgentId(params: {
requestedAgentId: string | undefined;
requestedWorkspace?: string;
deps?: CrestodianCommandDeps;
}): Promise<string | undefined> {
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForOperation(params.deps);
const workspace = params.requestedWorkspace
? resolveUserPath(params.requestedWorkspace)
: undefined;
@@ -429,14 +454,12 @@ export async function executeCrestodianOperation(
return { applied: false, exitsInteractive: operation.message.includes("Bye.") };
}
if (operation.kind === "overview") {
const { formatCrestodianOverview, loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
runtime.log(formatCrestodianOverview(overview));
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(await formatOverviewForOperation(overview, opts.deps));
return { applied: false };
}
if (operation.kind === "agents") {
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(
[
"Agents:",
@@ -456,8 +479,7 @@ export async function executeCrestodianOperation(
return { applied: false };
}
if (operation.kind === "models") {
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(
[
`Default model: ${overview.defaultModel ?? "not configured"}`,
@@ -480,8 +502,7 @@ export async function executeCrestodianOperation(
return { applied: false };
}
if (operation.kind === "setup") {
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForOperation(opts.deps);
const setupModel = chooseSetupModel(overview, operation.model);
if (!opts.approved) {
const message = [
@@ -720,8 +741,7 @@ export async function executeCrestodianOperation(
return { applied: false };
}
if (operation.kind === "gateway-status") {
const { loadCrestodianOverview } = await import("./overview.js");
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(formatGatewayStatusLine(overview));
return { applied: false };
}
@@ -782,6 +802,7 @@ export async function executeCrestodianOperation(
const agentId = await resolveTuiAgentId({
requestedAgentId: operation.agentId,
requestedWorkspace: operation.workspace,
deps: opts.deps,
});
const session = agentId ? buildAgentMainSessionKey({ agentId }) : undefined;
const runTui = opts.deps?.runTui ?? (await import("../tui/tui.js")).runTui;

View File

@@ -1,58 +1,34 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
const mocks = vi.hoisted(() => ({
runTui: vi.fn(async (_opts: unknown) => ({ exitReason: "exit" as const })),
}));
vi.mock("../tui/tui.js", () => ({
runTui: mocks.runTui,
}));
vi.mock("./probes.js", () => ({
probeLocalCommand: vi.fn(async (command: string) => ({
command,
found: false,
error: "not found",
})),
probeGatewayUrl: vi.fn(async (url: string) => ({ reachable: false, url, error: "offline" })),
}));
vi.mock("./overview.js", () => ({
formatCrestodianOverview: () => "Default model: openai/gpt-5.5",
formatCrestodianStartupMessage: () => "Default model: openai/gpt-5.5",
loadCrestodianOverview: vi.fn(async () => ({
defaultAgentId: "main",
defaultModel: "openai/gpt-5.5",
agents: [{ id: "main", isDefault: true, model: "openai/gpt-5.5" }],
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
tools: {
codex: { command: "codex", found: false, error: "not found" },
claude: { command: "claude", found: false, error: "not found" },
apiKeys: { openai: true, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "local loopback",
reachable: false,
error: "offline",
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
})),
}));
import type { CrestodianOverview } from "./overview.js";
import { runCrestodianTui } from "./tui-backend.js";
const overview: CrestodianOverview = {
defaultAgentId: "main",
defaultModel: "openai/gpt-5.5",
agents: [{ id: "main", isDefault: true, model: "openai/gpt-5.5" }],
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
tools: {
codex: { command: "codex", found: false, error: "not found" },
claude: { command: "claude", found: false, error: "not found" },
apiKeys: { openai: true, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "local loopback",
reachable: false,
error: "offline",
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
};
function createRuntime(): RuntimeEnv {
return {
log: vi.fn(),
error: vi.fn(),
log: () => undefined,
error: () => undefined,
exit: (code) => {
throw new Error(`exit ${code}`);
},
@@ -60,32 +36,32 @@ function createRuntime(): RuntimeEnv {
}
describe("runCrestodianTui", () => {
beforeEach(() => {
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
});
afterEach(() => {
vi.unstubAllEnvs();
mocks.runTui.mockClear();
});
it("runs Crestodian inside the shared TUI shell", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-tui-"));
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(tempDir, "openclaw.json"));
let runTuiCalls = 0;
let runTuiOptions: unknown;
await runCrestodianTui({}, createRuntime());
expect(mocks.runTui).toHaveBeenCalledWith(
expect.objectContaining({
local: true,
session: "agent:crestodian:main",
historyLimit: 200,
config: {},
title: "openclaw crestodian",
}),
await runCrestodianTui(
{
deps: {
loadOverview: async () => overview,
},
runTui: async (opts) => {
runTuiCalls += 1;
runTuiOptions = opts;
return { exitReason: "exit" };
},
},
createRuntime(),
);
const callOptions = mocks.runTui.mock.calls[0]?.[0] as { backend?: unknown } | undefined;
expect(callOptions?.backend).toBeTruthy();
expect(runTuiCalls).toBe(1);
expect(runTuiOptions).toMatchObject({
local: true,
session: "agent:crestodian:main",
historyLimit: 200,
config: {},
title: "openclaw crestodian",
});
expect((runTuiOptions as { backend?: unknown }).backend).toBeTruthy();
});
});

View File

@@ -10,7 +10,7 @@ import type {
TuiModelChoice,
TuiSessionList,
} from "../tui/tui-backend.js";
import { runTui } from "../tui/tui.js";
import { runTui as defaultRunTui } from "../tui/tui.js";
import type { CrestodianAssistantPlanner } from "./assistant.js";
import { approvalQuestion, isYes, resolveCrestodianOperation } from "./dialogue.js";
import {
@@ -21,10 +21,13 @@ import {
} from "./operations.js";
import { formatCrestodianStartupMessage, loadCrestodianOverview } from "./overview.js";
type RunTui = typeof defaultRunTui;
export type CrestodianTuiOptions = {
yes?: boolean;
deps?: CrestodianCommandDeps;
planWithAssistant?: CrestodianAssistantPlanner;
runTui?: RunTui;
};
type CrestodianHistoryMessage = {
@@ -52,6 +55,13 @@ function createCaptureRuntime(): CaptureRuntime {
};
}
async function loadOverviewForTui(opts: CrestodianTuiOptions) {
if (opts.deps?.loadOverview) {
return await opts.deps.loadOverview();
}
return await loadCrestodianOverview();
}
function message(role: "assistant" | "user", text: string): CrestodianHistoryMessage {
return {
role,
@@ -143,7 +153,7 @@ class CrestodianTuiBackend implements TuiBackend {
}
async listSessions(): Promise<TuiSessionList> {
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForTui(this.opts);
const model = splitModelRef(overview.defaultModel);
return {
ts: Date.now(),
@@ -200,7 +210,7 @@ class CrestodianTuiBackend implements TuiBackend {
async resetSession(): Promise<{ ok: boolean }> {
this.pending = null;
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForTui(this.opts);
this.messages.splice(
0,
this.messages.length,
@@ -210,7 +220,7 @@ class CrestodianTuiBackend implements TuiBackend {
}
async getGatewayStatus(): Promise<string> {
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForTui(this.opts);
return overview.gateway.reachable ? "Gateway reachable" : "Gateway unreachable";
}
@@ -316,8 +326,9 @@ export async function runCrestodianTui(
): Promise<void> {
let nextInput: string | undefined;
for (;;) {
const overview = await loadCrestodianOverview();
const overview = await loadOverviewForTui(opts);
const backend = new CrestodianTuiBackend(opts, formatCrestodianStartupMessage(overview));
const runTui = opts.runTui ?? defaultRunTui;
await runTui({
local: true,
session: CRESTODIAN_SESSION_KEY,

View File

@@ -1,60 +1,64 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import { tryHandleRootHelpFastPath } from "./entry.js";
const outputPrecomputedRootHelpTextMock = vi.hoisted(() => vi.fn(() => false));
vi.mock("./cli/root-help-metadata.js", () => ({
outputPrecomputedRootHelpText: outputPrecomputedRootHelpTextMock,
}));
describe("entry root help fast path", () => {
it("prefers precomputed root help text when available", async () => {
outputPrecomputedRootHelpTextMock.mockReturnValueOnce(true);
let outputPrecomputedRootHelpTextCalls = 0;
const handled = await tryHandleRootHelpFastPath(["node", "openclaw", "--help"], {
env: {},
outputPrecomputedRootHelpText: () => {
outputPrecomputedRootHelpTextCalls += 1;
return true;
},
});
expect(handled).toBe(true);
expect(outputPrecomputedRootHelpTextMock).toHaveBeenCalledTimes(1);
expect(outputPrecomputedRootHelpTextCalls).toBe(1);
});
it("renders root help without importing the full program", async () => {
const outputRootHelpMock = vi.fn();
let outputRootHelpCalls = 0;
const handled = await tryHandleRootHelpFastPath(["node", "openclaw", "--help"], {
outputRootHelp: outputRootHelpMock,
outputRootHelp: () => {
outputRootHelpCalls += 1;
},
env: {},
});
expect(handled).toBe(true);
expect(outputRootHelpMock).toHaveBeenCalledTimes(1);
expect(outputRootHelpCalls).toBe(1);
});
it("ignores non-root help invocations", async () => {
const outputRootHelpMock = vi.fn();
let outputRootHelpCalls = 0;
const handled = await tryHandleRootHelpFastPath(["node", "openclaw", "status", "--help"], {
outputRootHelp: outputRootHelpMock,
outputRootHelp: () => {
outputRootHelpCalls += 1;
},
env: {},
});
expect(handled).toBe(false);
expect(outputRootHelpMock).not.toHaveBeenCalled();
expect(outputRootHelpCalls).toBe(0);
});
it("skips the host help fast path when a container target is active", async () => {
const outputRootHelpMock = vi.fn();
let outputRootHelpCalls = 0;
const handled = await tryHandleRootHelpFastPath(
["node", "openclaw", "--container", "demo", "--help"],
{
outputRootHelp: outputRootHelpMock,
outputRootHelp: () => {
outputRootHelpCalls += 1;
},
env: {},
},
);
expect(handled).toBe(false);
expect(outputRootHelpMock).not.toHaveBeenCalled();
expect(outputRootHelpCalls).toBe(0);
});
});

View File

@@ -134,6 +134,7 @@ if (
export async function tryHandleRootHelpFastPath(
argv: string[],
deps: {
outputPrecomputedRootHelpText?: () => boolean;
outputRootHelp?: () => void | Promise<void>;
onError?: (error: unknown) => void;
env?: NodeJS.ProcessEnv;
@@ -159,7 +160,9 @@ export async function tryHandleRootHelpFastPath(
await deps.outputRootHelp();
return true;
}
const { outputPrecomputedRootHelpText } = await import("./cli/root-help-metadata.js");
const outputPrecomputedRootHelpText =
deps.outputPrecomputedRootHelpText ??
(await import("./cli/root-help-metadata.js")).outputPrecomputedRootHelpText;
if (!outputPrecomputedRootHelpText()) {
const { outputRootHelp } = await import("./cli/program/root-help.js");
await outputRootHelp();

View File

@@ -1,10 +1,7 @@
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import ts from "typescript";
import { describe, expect, it } from "vitest";
const libraryPath = resolve(dirname(fileURLToPath(import.meta.url)), "library.ts");
const libraryPath = new URL("./library.ts", import.meta.url);
const lazyRuntimeSpecifiers = [
"./auto-reply/reply.runtime.js",
"./cli/prompt.js",
@@ -15,32 +12,17 @@ const lazyRuntimeSpecifiers = [
function readLibraryModuleImports() {
const sourceText = readFileSync(libraryPath, "utf8");
const sourceFile = ts.createSourceFile(libraryPath, sourceText, ts.ScriptTarget.Latest, true);
const staticImports = new Set<string>();
const dynamicImports = new Set<string>();
const staticImportPattern = /(?:^|\n)\s*import\s+(?!type\b)[\s\S]*?\s+from\s+["']([^"']+)["']/g;
const dynamicImportPattern = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
function visit(node: ts.Node) {
if (
ts.isImportDeclaration(node) &&
ts.isStringLiteral(node.moduleSpecifier) &&
!node.importClause?.isTypeOnly
) {
staticImports.add(node.moduleSpecifier.text);
}
if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
node.arguments.length === 1 &&
ts.isStringLiteral(node.arguments[0])
) {
dynamicImports.add(node.arguments[0].text);
}
ts.forEachChild(node, visit);
for (const match of sourceText.matchAll(staticImportPattern)) {
staticImports.add(match[1]);
}
for (const match of sourceText.matchAll(dynamicImportPattern)) {
dynamicImports.add(match[1]);
}
visit(sourceFile);
return { dynamicImports, staticImports };
}

View File

@@ -25,10 +25,13 @@ describe("ensureDir", () => {
describe("sleep", () => {
it("resolves after delay using fake timers", async () => {
vi.useFakeTimers();
const promise = sleep(1000);
vi.advanceTimersByTime(1000);
await expect(promise).resolves.toBeUndefined();
vi.useRealTimers();
try {
const promise = sleep(1000);
vi.advanceTimersByTime(1000);
await expect(promise).resolves.toBeUndefined();
} finally {
vi.useRealTimers();
}
});
});
@@ -65,10 +68,11 @@ describe("resolveHomeDir", () => {
it("prefers OPENCLAW_HOME over HOME", () => {
vi.stubEnv("OPENCLAW_HOME", "/srv/openclaw-home");
vi.stubEnv("HOME", "/home/other");
expect(resolveHomeDir()).toBe(path.resolve("/srv/openclaw-home"));
vi.unstubAllEnvs();
try {
expect(resolveHomeDir()).toBe(path.resolve("/srv/openclaw-home"));
} finally {
vi.unstubAllEnvs();
}
});
});
@@ -76,12 +80,13 @@ describe("shortenHomePath", () => {
it("uses $OPENCLAW_HOME prefix when OPENCLAW_HOME is set", () => {
vi.stubEnv("OPENCLAW_HOME", "/srv/openclaw-home");
vi.stubEnv("HOME", "/home/other");
expect(shortenHomePath(`${path.resolve("/srv/openclaw-home")}/.openclaw/openclaw.json`)).toBe(
"$OPENCLAW_HOME/.openclaw/openclaw.json",
);
vi.unstubAllEnvs();
try {
expect(shortenHomePath(`${path.resolve("/srv/openclaw-home")}/.openclaw/openclaw.json`)).toBe(
"$OPENCLAW_HOME/.openclaw/openclaw.json",
);
} finally {
vi.unstubAllEnvs();
}
});
});
@@ -89,12 +94,15 @@ describe("shortenHomeInString", () => {
it("uses $OPENCLAW_HOME replacement when OPENCLAW_HOME is set", () => {
vi.stubEnv("OPENCLAW_HOME", "/srv/openclaw-home");
vi.stubEnv("HOME", "/home/other");
expect(
shortenHomeInString(`config: ${path.resolve("/srv/openclaw-home")}/.openclaw/openclaw.json`),
).toBe("config: $OPENCLAW_HOME/.openclaw/openclaw.json");
vi.unstubAllEnvs();
try {
expect(
shortenHomeInString(
`config: ${path.resolve("/srv/openclaw-home")}/.openclaw/openclaw.json`,
),
).toBe("config: $OPENCLAW_HOME/.openclaw/openclaw.json");
} finally {
vi.unstubAllEnvs();
}
});
});
@@ -116,10 +124,11 @@ describe("resolveUserPath", () => {
it("prefers OPENCLAW_HOME for tilde expansion", () => {
vi.stubEnv("OPENCLAW_HOME", "/srv/openclaw-home");
vi.stubEnv("HOME", "/home/other");
expect(resolveUserPath("~/openclaw")).toBe(path.resolve("/srv/openclaw-home", "openclaw"));
vi.unstubAllEnvs();
try {
expect(resolveUserPath("~/openclaw")).toBe(path.resolve("/srv/openclaw-home", "openclaw"));
} finally {
vi.unstubAllEnvs();
}
});
it("uses the provided env for tilde expansion", () => {

View File

@@ -62,19 +62,27 @@ export const forcedUnitFastTestFiles = [
"packages/memory-host-sdk/src/host/session-files.test.ts",
"src/acp/client.test.ts",
"src/acp/control-plane/manager.test.ts",
"src/acp/translator.cancel-scoping.test.ts",
"src/acp/translator.stop-reason.test.ts",
"src/acp/persistent-bindings.test.ts",
"src/acp/server.startup.test.ts",
"src/acp/translator.session-rate-limit.test.ts",
"src/browser-lifecycle-cleanup.test.ts",
"src/crestodian/overview.test.ts",
"src/crestodian/crestodian.test.ts",
"src/crestodian/operations.test.ts",
"src/crestodian/rescue-message.test.ts",
"src/crestodian/tui-backend.test.ts",
"src/flows/channel-setup.test.ts",
"src/context-engine/context-engine.test.ts",
"src/canvas-host/server.state-dir.test.ts",
"src/docker-image-digests.test.ts",
"src/dockerfile.test.ts",
"src/entry.test.ts",
"src/i18n/registry.test.ts",
"src/install-sh-version.test.ts",
"src/logger.test.ts",
"src/library.test.ts",
"src/memory-host-sdk/host/internal.test.ts",
"src/memory-host-sdk/host/batch-http.test.ts",
"src/memory-host-sdk/host/backend-config.test.ts",
@@ -84,9 +92,13 @@ export const forcedUnitFastTestFiles = [
"src/mcp/channel-server.shutdown-unhandled-rejection.test.ts",
"src/node-host/invoke-system-run-plan.test.ts",
"src/node-host/invoke-system-run.test.ts",
"src/pairing/allow-from-store-read.test.ts",
"src/pairing/pairing-store.test.ts",
"src/plugin-sdk/memory-host-events.test.ts",
"src/proxy-capture/store.sqlite.test.ts",
"src/security/audit-exec-surface.test.ts",
"src/security/audit-extra.async.test.ts",
"src/security/dm-policy-shared.test.ts",
"src/security/audit-plugins-trust.test.ts",
"src/security/audit-workspace-skill-escape.test.ts",
"src/security/external-content.test.ts",
@@ -94,10 +106,13 @@ export const forcedUnitFastTestFiles = [
"src/security/skill-scanner.test.ts",
"src/security/windows-acl.test.ts",
"src/realtime-transcription/websocket-session.test.ts",
"src/routing/resolve-route.test.ts",
"src/trajectory/export.test.ts",
"src/tts/provider-registry.test.ts",
"src/tts/status-config.test.ts",
"src/terminal/table.test.ts",
"src/test-helpers/state-dir-env.test.ts",
"src/utils.test.ts",
"src/version.test.ts",
];
const forcedUnitFastTestFileSet = new Set(forcedUnitFastTestFiles);