From f40bd567933a39d89765cb99c90858ab21ab91ce Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 23:48:46 +0100 Subject: [PATCH] test: isolate agent hotspot scans --- src/agents/cli-runner/bundle-mcp.test.ts | 5 + src/agents/context.eager-warmup.test.ts | 7 - ...-config.providers.anthropic-vertex.test.ts | 118 --------------- src/agents/skills-install.test.ts | 35 ++++- src/agents/skills-install.ts | 14 +- ...erged-skills-into-target-workspace.test.ts | 9 +- .../subagent-spawn.thread-binding.test.ts | 138 +++++++++--------- 7 files changed, 127 insertions(+), 199 deletions(-) delete mode 100644 src/agents/models-config.providers.anthropic-vertex.test.ts diff --git a/src/agents/cli-runner/bundle-mcp.test.ts b/src/agents/cli-runner/bundle-mcp.test.ts index 23e47483adf..25547055ed3 100644 --- a/src/agents/cli-runner/bundle-mcp.test.ts +++ b/src/agents/cli-runner/bundle-mcp.test.ts @@ -14,14 +14,19 @@ const tempHarness = createBundleMcpTempHarness(); let bundleProbeHomeDir = ""; let bundleProbeWorkspaceDir = ""; let bundleProbeServerPath = ""; +let envSnapshot: ReturnType | 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(); }); diff --git a/src/agents/context.eager-warmup.test.ts b/src/agents/context.eager-warmup.test.ts index 6be84291bcf..773a93c02fe 100644 --- a/src/agents/context.eager-warmup.test.ts +++ b/src/agents/context.eager-warmup.test.ts @@ -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(); - }); }); diff --git a/src/agents/models-config.providers.anthropic-vertex.test.ts b/src/agents/models-config.providers.anthropic-vertex.test.ts deleted file mode 100644 index fe15ecdc05e..00000000000 --- a/src/agents/models-config.providers.anthropic-vertex.test.ts +++ /dev/null @@ -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, - run: (params: { agentDir: string; credentialsPath: string }) => Promise, -) { - 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 }); - } - }); -}); diff --git a/src/agents/skills-install.test.ts b/src/agents/skills-install.test.ts index 8555f51acb7..735fe74b9dd 100644 --- a/src/agents/skills-install.test.ts +++ b/src/agents/skills-install.test.ts @@ -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({ diff --git a/src/agents/skills-install.ts b/src/agents/skills-install.ts index d5cb4f5a2bd..a55f865a7c6 100644 --- a/src/agents/skills-install.ts +++ b/src/agents/skills-install.ts @@ -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 { 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 { const buildPrompt = ( workspaceDir: string, opts?: Parameters[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"); diff --git a/src/agents/subagent-spawn.thread-binding.test.ts b/src/agents/subagent-spawn.thread-binding.test.ts index 6e690f19bf7..bede7a929e3 100644 --- a/src/agents/subagent-spawn.thread-binding.test.ts +++ b/src/agents/subagent-spawn.thread-binding.test.ts @@ -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[0]> = {}, - ) { - return loadSubagentSpawnModuleForTest({ + type SpawnModule = Awaited>; + type SessionBindingService = NonNullable< + Parameters[0]["getSessionBindingService"] + >; + type DeliveryTargetResolver = NonNullable< + Parameters[0]["resolveConversationDeliveryTarget"] + >; + + let spawnSubagentDirect: SpawnModule["spawnSubagentDirect"]; + let currentConfig: Record; + let currentSessionBindingService: ReturnType; + 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(