test: isolate agent hotspot scans

This commit is contained in:
Peter Steinberger
2026-04-18 23:48:46 +01:00
parent 473225c471
commit f40bd56793
7 changed files with 127 additions and 199 deletions

View File

@@ -14,14 +14,19 @@ const tempHarness = createBundleMcpTempHarness();
let bundleProbeHomeDir = "";
let bundleProbeWorkspaceDir = "";
let bundleProbeServerPath = "";
let envSnapshot: ReturnType<typeof captureEnv> | undefined;
beforeAll(async () => {
envSnapshot = captureEnv(["OPENCLAW_BUNDLED_PLUGINS_DIR"]);
bundleProbeHomeDir = await tempHarness.createTempDir("openclaw-cli-bundle-mcp-home-");
bundleProbeWorkspaceDir = await tempHarness.createTempDir("openclaw-cli-bundle-mcp-workspace-");
const emptyBundledDir = await tempHarness.createTempDir("openclaw-cli-bundle-mcp-bundled-");
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = emptyBundledDir;
({ serverPath: bundleProbeServerPath } = await createBundleProbePlugin(bundleProbeHomeDir));
});
afterAll(async () => {
envSnapshot?.restore();
await tempHarness.cleanup();
});

View File

@@ -25,11 +25,4 @@ describe("agents/context eager warmup", () => {
expect(loadConfigMock).not.toHaveBeenCalled();
});
it("does not eager-load config when plugin-sdk command-auth is imported", async () => {
process.argv = ["node", "openclaw", "onboard"];
await importFreshModule(import.meta.url, "../plugin-sdk/command-auth.js?scope=onboard");
expect(loadConfigMock).not.toHaveBeenCalled();
});
});

View File

@@ -1,118 +0,0 @@
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js";
const ANTHROPIC_VERTEX_DISCOVERY_ENV = {
OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS: "anthropic",
} satisfies NodeJS.ProcessEnv;
async function withAdcCredentialsFile(
credentials: Record<string, string>,
run: (params: { agentDir: string; credentialsPath: string }) => Promise<void>,
) {
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
const adcDir = mkdtempSync(join(tmpdir(), "openclaw-adc-"));
const credentialsPath = join(adcDir, "application_default_credentials.json");
writeFileSync(credentialsPath, JSON.stringify(credentials), "utf8");
try {
await run({ agentDir, credentialsPath });
} finally {
rmSync(agentDir, { recursive: true, force: true });
rmSync(adcDir, { recursive: true, force: true });
}
}
describe("anthropic-vertex implicit provider", () => {
it("does not auto-enable from GOOGLE_CLOUD_PROJECT_ID alone", async () => {
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
try {
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {
...ANTHROPIC_VERTEX_DISCOVERY_ENV,
GOOGLE_CLOUD_PROJECT_ID: "vertex-project",
},
});
expect(providers?.["anthropic-vertex"]).toBeUndefined();
} finally {
rmSync(agentDir, { recursive: true, force: true });
}
});
it("accepts ADC credentials when the file includes a project_id", async () => {
await withAdcCredentialsFile(
{ project_id: "vertex-project" },
async ({ agentDir, credentialsPath }) => {
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {
...ANTHROPIC_VERTEX_DISCOVERY_ENV,
GOOGLE_APPLICATION_CREDENTIALS: credentialsPath,
GOOGLE_CLOUD_LOCATION: "us-east1",
},
});
expect(providers?.["anthropic-vertex"]?.baseUrl).toBe(
"https://us-east1-aiplatform.googleapis.com",
);
expect(providers?.["anthropic-vertex"]?.models).toMatchObject([
{ id: "claude-opus-4-6", maxTokens: 128000, contextWindow: 1_000_000 },
{ id: "claude-sonnet-4-6", maxTokens: 128000, contextWindow: 1_000_000 },
]);
},
);
});
it("accepts explicit metadata auth opt-in without local credential files", async () => {
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
try {
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {
...ANTHROPIC_VERTEX_DISCOVERY_ENV,
ANTHROPIC_VERTEX_USE_GCP_METADATA: "true",
GOOGLE_CLOUD_LOCATION: "us-east5",
},
});
expect(providers?.["anthropic-vertex"]?.baseUrl).toBe(
"https://us-east5-aiplatform.googleapis.com",
);
} finally {
rmSync(agentDir, { recursive: true, force: true });
}
});
it("merges the bundled catalog into explicit anthropic-vertex provider overrides", async () => {
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
try {
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {
...ANTHROPIC_VERTEX_DISCOVERY_ENV,
ANTHROPIC_VERTEX_USE_GCP_METADATA: "true",
GOOGLE_CLOUD_LOCATION: "us-east5",
},
explicitProviders: {
"anthropic-vertex": {
baseUrl: "https://europe-west4-aiplatform.googleapis.com",
headers: { "x-test-header": "1" },
models: [],
},
},
});
expect(providers?.["anthropic-vertex"]?.baseUrl).toBe(
"https://europe-west4-aiplatform.googleapis.com",
);
expect(providers?.["anthropic-vertex"]?.headers).toEqual({ "x-test-header": "1" });
expect(providers?.["anthropic-vertex"]?.models?.map((model) => model.id)).toEqual([
"claude-opus-4-6",
"claude-sonnet-4-6",
]);
} finally {
rmSync(agentDir, { recursive: true, force: true });
}
});
});

View File

@@ -8,11 +8,14 @@ import {
import { createMockPluginRegistry } from "../plugins/hooks.test-helpers.js";
import { captureEnv } from "../test-utils/env.js";
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
import { installSkill } from "./skills-install.js";
import { installSkill, __testing as skillsInstallTesting } from "./skills-install.js";
import {
runCommandWithTimeoutMock,
scanDirectoryWithSummaryMock,
} from "./skills-install.test-mocks.js";
import { resolveOpenClawMetadata, resolveSkillInvocationPolicy } from "./skills/frontmatter.js";
import { loadSkillsFromDirSafe, readSkillFrontmatterSafe } from "./skills/local-loader.js";
import type { SkillEntry } from "./skills/types.js";
vi.mock("../process/exec.js", () => ({
runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args),
@@ -45,6 +48,32 @@ metadata: {"openclaw":{"install":[{"id":"deps","kind":"node","package":"example-
return skillDir;
}
function loadTestWorkspaceSkillEntries(workspaceDir: string): SkillEntry[] {
const skills = loadSkillsFromDirSafe({
dir: path.join(workspaceDir, "skills"),
source: "openclaw-workspace",
}).skills;
return skills.map((skill) => {
const frontmatter =
readSkillFrontmatterSafe({
rootDir: skill.baseDir,
filePath: skill.filePath,
}) ?? {};
const invocation = resolveSkillInvocationPolicy(frontmatter);
return {
skill,
frontmatter,
metadata: resolveOpenClawMetadata(frontmatter),
invocation,
exposure: {
includeInRuntimeRegistry: true,
includeInAvailableSkillsPrompt: !invocation.disableModelInvocation,
userInvocable: invocation.userInvocable,
},
};
});
}
const workspaceSuite = createFixtureSuite("openclaw-skills-install-");
beforeAll(async () => {
@@ -53,6 +82,7 @@ beforeAll(async () => {
afterAll(async () => {
resetGlobalHookRunner();
skillsInstallTesting.setDepsForTest();
await workspaceSuite.cleanup();
});
@@ -73,6 +103,9 @@ async function withWorkspaceCase(
describe("installSkill code safety scanning", () => {
beforeEach(() => {
resetGlobalHookRunner();
skillsInstallTesting.setDepsForTest({
loadWorkspaceSkillEntries: loadTestWorkspaceSkillEntries,
});
runCommandWithTimeoutMock.mockClear();
scanDirectoryWithSummaryMock.mockClear();
runCommandWithTimeoutMock.mockResolvedValue({

View File

@@ -15,8 +15,8 @@ import { formatInstallFailureMessage } from "./skills-install-output.js";
import type { SkillInstallResult } from "./skills-install.types.js";
import {
hasBinary as defaultHasBinary,
loadWorkspaceSkillEntries,
resolveSkillsInstallPreferences,
loadWorkspaceSkillEntries as defaultLoadWorkspaceSkillEntries,
resolveSkillsInstallPreferences as defaultResolveSkillsInstallPreferences,
type SkillEntry,
type SkillInstallSpec,
type SkillsInstallPreferences,
@@ -34,12 +34,16 @@ export type { SkillInstallResult } from "./skills-install.types.js";
type SkillsInstallDeps = {
hasBinary: (bin: string) => boolean;
loadWorkspaceSkillEntries: typeof defaultLoadWorkspaceSkillEntries;
resolveBrewExecutable: () => string | undefined;
resolveSkillsInstallPreferences: typeof defaultResolveSkillsInstallPreferences;
};
const defaultSkillsInstallDeps: SkillsInstallDeps = {
hasBinary: defaultHasBinary,
loadWorkspaceSkillEntries: defaultLoadWorkspaceSkillEntries,
resolveBrewExecutable: defaultResolveBrewExecutable,
resolveSkillsInstallPreferences: defaultResolveSkillsInstallPreferences,
};
let skillsInstallDeps = defaultSkillsInstallDeps;
@@ -419,7 +423,8 @@ async function executeInstallCommand(params: {
export async function installSkill(params: SkillInstallRequest): Promise<SkillInstallResult> {
const timeoutMs = Math.min(Math.max(params.timeoutMs ?? 300_000, 1_000), 900_000);
const workspaceDir = resolveUserPath(params.workspaceDir);
const entries = loadWorkspaceSkillEntries(workspaceDir);
const deps = getSkillsInstallDeps();
const entries = deps.loadWorkspaceSkillEntries(workspaceDir);
const entry = entries.find((item) => item.skill.name === params.skillName);
if (!entry) {
return {
@@ -483,7 +488,7 @@ export async function installSkill(params: SkillInstallRequest): Promise<SkillIn
return withWarnings(downloadResult, warnings);
}
const prefs = resolveSkillsInstallPreferences(params.config);
const prefs = deps.resolveSkillsInstallPreferences(params.config);
const command = buildInstallCommand(spec, prefs);
if (command.error) {
return withWarnings(
@@ -498,7 +503,6 @@ export async function installSkill(params: SkillInstallRequest): Promise<SkillIn
);
}
const deps = getSkillsInstallDeps();
const brewExe = deps.hasBinary("brew") ? "brew" : deps.resolveBrewExecutable();
if (spec.kind === "brew" && !brewExe) {
return withWarnings(resolveBrewMissingFailure(spec), warnings);

View File

@@ -87,7 +87,14 @@ describe("buildWorkspaceSkillsPrompt", () => {
const buildPrompt = (
workspaceDir: string,
opts?: Parameters<typeof buildWorkspaceSkillsPrompt>[1],
) => withEnv({ HOME: workspaceDir }, () => buildWorkspaceSkillsPrompt(workspaceDir, opts));
) =>
withEnv({ HOME: workspaceDir }, () =>
buildWorkspaceSkillsPrompt(workspaceDir, {
bundledSkillsDir: path.join(workspaceDir, ".bundled"),
managedSkillsDir: path.join(workspaceDir, ".managed"),
...opts,
}),
);
const cloneSourceTemplate = async () => {
const sourceWorkspace = await createCaseDir("source");

View File

@@ -1,5 +1,5 @@
import os from "node:os";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
createSubagentSpawnTestConfig,
installSessionStoreCaptureMock,
@@ -19,31 +19,47 @@ const hoisted = vi.hoisted(() => ({
}));
describe("spawnSubagentDirect thread binding delivery", () => {
function loadThreadBindingSpawnModule(
overrides: Partial<Parameters<typeof loadSubagentSpawnModuleForTest>[0]> = {},
) {
return loadSubagentSpawnModuleForTest({
type SpawnModule = Awaited<ReturnType<typeof loadSubagentSpawnModuleForTest>>;
type SessionBindingService = NonNullable<
Parameters<typeof loadSubagentSpawnModuleForTest>[0]["getSessionBindingService"]
>;
type DeliveryTargetResolver = NonNullable<
Parameters<typeof loadSubagentSpawnModuleForTest>[0]["resolveConversationDeliveryTarget"]
>;
let spawnSubagentDirect: SpawnModule["spawnSubagentDirect"];
let currentConfig: Record<string, unknown>;
let currentSessionBindingService: ReturnType<SessionBindingService>;
let currentDeliveryTargetResolver: DeliveryTargetResolver;
beforeAll(async () => {
({ spawnSubagentDirect } = await loadSubagentSpawnModuleForTest({
callGatewayMock: hoisted.callGatewayMock,
loadConfig: () =>
createSubagentSpawnTestConfig(os.tmpdir(), {
agents: {
defaults: {
workspace: os.tmpdir(),
},
list: [{ id: "main", workspace: "/tmp/workspace-main" }],
},
}),
loadConfig: () => currentConfig,
updateSessionStoreMock: hoisted.updateSessionStoreMock,
registerSubagentRunMock: hoisted.registerSubagentRunMock,
emitSessionLifecycleEventMock: hoisted.emitSessionLifecycleEventMock,
hookRunner: hoisted.hookRunner,
resolveSubagentSpawnModelSelection: () => "openai-codex/gpt-5.4",
resolveSandboxRuntimeStatus: () => ({ sandboxed: false }),
...overrides,
});
}
getSessionBindingService: () => currentSessionBindingService,
resolveConversationDeliveryTarget: (params) => currentDeliveryTargetResolver(params),
}));
});
beforeEach(() => {
currentConfig = createSubagentSpawnTestConfig(os.tmpdir(), {
agents: {
defaults: {
workspace: os.tmpdir(),
},
list: [{ id: "main", workspace: "/tmp/workspace-main" }],
},
});
currentSessionBindingService = { listBySession: () => [] };
currentDeliveryTargetResolver = (params) => ({
to: params.conversationId ? `channel:${String(params.conversationId)}` : undefined,
});
hoisted.callGatewayMock.mockReset();
hoisted.updateSessionStoreMock.mockReset();
hoisted.registerSubagentRunMock.mockReset();
@@ -83,43 +99,33 @@ describe("spawnSubagentDirect thread binding delivery", () => {
},
};
});
const { spawnSubagentDirect } = await loadSubagentSpawnModuleForTest({
callGatewayMock: hoisted.callGatewayMock,
loadConfig: () =>
createSubagentSpawnTestConfig(os.tmpdir(), {
agents: {
defaults: {
workspace: os.tmpdir(),
subagents: {
allowAgents: ["bot-alpha"],
},
},
list: [
{ id: "main", workspace: "/tmp/workspace-main" },
{ id: "bot-alpha", workspace: "/tmp/workspace-bot-alpha" },
],
currentConfig = createSubagentSpawnTestConfig(os.tmpdir(), {
agents: {
defaults: {
workspace: os.tmpdir(),
subagents: {
allowAgents: ["bot-alpha"],
},
bindings: [
{
type: "route",
agentId: "bot-alpha",
match: {
channel: "matrix",
peer: {
kind: "channel",
id: boundRoom,
},
accountId: "bot-alpha",
},
},
list: [
{ id: "main", workspace: "/tmp/workspace-main" },
{ id: "bot-alpha", workspace: "/tmp/workspace-bot-alpha" },
],
},
bindings: [
{
type: "route",
agentId: "bot-alpha",
match: {
channel: "matrix",
peer: {
kind: "channel",
id: boundRoom,
},
],
}),
updateSessionStoreMock: hoisted.updateSessionStoreMock,
registerSubagentRunMock: hoisted.registerSubagentRunMock,
emitSessionLifecycleEventMock: hoisted.emitSessionLifecycleEventMock,
hookRunner: hoisted.hookRunner,
resolveSubagentSpawnModelSelection: () => "openai-codex/gpt-5.4",
resolveSandboxRuntimeStatus: () => ({ sandboxed: false }),
accountId: "bot-alpha",
},
},
],
});
const result = await spawnSubagentDirect(
@@ -175,22 +181,20 @@ describe("spawnSubagentDirect thread binding delivery", () => {
status: "ok",
threadBindingReady: true,
});
const { spawnSubagentDirect } = await loadThreadBindingSpawnModule({
getSessionBindingService: () => ({
listBySession: () => [
{
status: "active",
conversation: {
channel: "feishu",
accountId: "work",
conversationId: "oc_dm_chat_1",
},
currentSessionBindingService = {
listBySession: () => [
{
status: "active",
conversation: {
channel: "feishu",
accountId: "work",
conversationId: "oc_dm_chat_1",
},
],
}),
resolveConversationDeliveryTarget: () => ({
to: "channel:oc_dm_chat_1",
}),
},
],
};
currentDeliveryTargetResolver = () => ({
to: "channel:oc_dm_chat_1",
});
const result = await spawnSubagentDirect(