diff --git a/src/channels/plugins/session-conversation.bundled-fallback.test.ts b/src/channels/plugins/session-conversation.bundled-fallback.test.ts index d2044059bd0..26ac8628394 100644 --- a/src/channels/plugins/session-conversation.bundled-fallback.test.ts +++ b/src/channels/plugins/session-conversation.bundled-fallback.test.ts @@ -21,10 +21,14 @@ vi.mock("../../plugin-sdk/facade-runtime.js", () => ({ : null, })); -vi.mock("../../plugins/bundled-plugin-metadata.js", () => ({ - resolveBundledPluginPublicSurfacePath: ({ dirName }: { dirName: string }) => - dirName === fallbackState.activeDirName ? `/tmp/${dirName}/session-key-api.js` : null, -})); +vi.mock("../../plugins/bundled-plugin-metadata.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveBundledPluginPublicSurfacePath: ({ dirName }: { dirName: string }) => + dirName === fallbackState.activeDirName ? `/tmp/${dirName}/session-key-api.js` : null, + }; +}); import { resolveSessionConversationRef } from "./session-conversation.js"; diff --git a/src/plugins/bundled-capability-metadata.ts b/src/plugins/bundled-capability-metadata.ts index 27722cfa9dc..364433056b6 100644 --- a/src/plugins/bundled-capability-metadata.ts +++ b/src/plugins/bundled-capability-metadata.ts @@ -1,3 +1,6 @@ +import fs from "node:fs"; +import path from "node:path"; +import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js"; import { listBundledPluginManifestSnapshots } from "./bundled-manifest-snapshots.js"; export type BundledPluginContractSnapshot = { @@ -12,6 +15,27 @@ export type BundledPluginContractSnapshot = { toolNames: string[]; }; +function resolveBundledManifestSnapshotDir(): string | undefined { + const packageRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url }); + if (!packageRoot) { + return undefined; + } + for (const candidate of [ + path.join(packageRoot, "extensions"), + path.join(packageRoot, "dist", "extensions"), + path.join(packageRoot, "dist-runtime", "extensions"), + ]) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + return undefined; +} + +const BUNDLED_PLUGIN_MANIFEST_SNAPSHOTS = listBundledPluginManifestSnapshots({ + bundledDir: resolveBundledManifestSnapshotDir(), +}); + function uniqueStrings(values: readonly string[] | undefined): string[] { const result: string[] = []; const seen = new Set(); @@ -27,18 +51,17 @@ function uniqueStrings(values: readonly string[] | undefined): string[] { } export const BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS: readonly BundledPluginContractSnapshot[] = - listBundledPluginManifestSnapshots() - .map(({ manifest }) => ({ - pluginId: manifest.id, - cliBackendIds: uniqueStrings(manifest.cliBackends), - providerIds: uniqueStrings(manifest.providers), - speechProviderIds: uniqueStrings(manifest.contracts?.speechProviders), - mediaUnderstandingProviderIds: uniqueStrings(manifest.contracts?.mediaUnderstandingProviders), - imageGenerationProviderIds: uniqueStrings(manifest.contracts?.imageGenerationProviders), - webFetchProviderIds: uniqueStrings(manifest.contracts?.webFetchProviders), - webSearchProviderIds: uniqueStrings(manifest.contracts?.webSearchProviders), - toolNames: uniqueStrings(manifest.contracts?.tools), - })) + BUNDLED_PLUGIN_MANIFEST_SNAPSHOTS.map(({ manifest }) => ({ + pluginId: manifest.id, + cliBackendIds: uniqueStrings(manifest.cliBackends), + providerIds: uniqueStrings(manifest.providers), + speechProviderIds: uniqueStrings(manifest.contracts?.speechProviders), + mediaUnderstandingProviderIds: uniqueStrings(manifest.contracts?.mediaUnderstandingProviders), + imageGenerationProviderIds: uniqueStrings(manifest.contracts?.imageGenerationProviders), + webFetchProviderIds: uniqueStrings(manifest.contracts?.webFetchProviders), + webSearchProviderIds: uniqueStrings(manifest.contracts?.webSearchProviders), + toolNames: uniqueStrings(manifest.contracts?.tools), + })) .filter( (entry) => entry.cliBackendIds.length > 0 || @@ -107,22 +130,18 @@ export const BUNDLED_PROVIDER_PLUGIN_ID_ALIASES = Object.fromEntries( ) as Readonly>; export const BUNDLED_LEGACY_PLUGIN_ID_ALIASES = Object.fromEntries( - listBundledPluginManifestSnapshots() - .flatMap(({ manifest }) => - (manifest.legacyPluginIds ?? []).map( - (legacyPluginId) => [legacyPluginId, manifest.id] as const, - ), - ) - .toSorted(([left], [right]) => left.localeCompare(right)), + BUNDLED_PLUGIN_MANIFEST_SNAPSHOTS.flatMap(({ manifest }) => + (manifest.legacyPluginIds ?? []).map( + (legacyPluginId) => [legacyPluginId, manifest.id] as const, + ), + ).toSorted(([left], [right]) => left.localeCompare(right)), ) as Readonly>; export const BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS = Object.fromEntries( - listBundledPluginManifestSnapshots() - .flatMap(({ manifest }) => - (manifest.autoEnableWhenConfiguredProviders ?? []).map((providerId) => [ - providerId, - manifest.id, - ]), - ) - .toSorted(([left], [right]) => left.localeCompare(right)), + BUNDLED_PLUGIN_MANIFEST_SNAPSHOTS.flatMap(({ manifest }) => + (manifest.autoEnableWhenConfiguredProviders ?? []).map((providerId) => [ + providerId, + manifest.id, + ]), + ).toSorted(([left], [right]) => left.localeCompare(right)), ) as Readonly>; diff --git a/src/secrets/runtime.web.integration.test.ts b/src/secrets/runtime.web.integration.test.ts deleted file mode 100644 index 08a70166c8c..00000000000 --- a/src/secrets/runtime.web.integration.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/config.js"; -import { - clearConfigCache, - clearRuntimeConfigSnapshot, - loadConfig, - writeConfigFile, -} from "../config/config.js"; -import { withTempHome } from "../config/home-env.test-harness.js"; -import { captureEnv } from "../test-utils/env.js"; -import { - asConfig, - loadAuthStoreWithProfiles, - SECRETS_RUNTIME_INTEGRATION_TIMEOUT_MS, -} from "./runtime.integration.test-helpers.js"; -import { - activateSecretsRuntimeSnapshot, - clearSecretsRuntimeSnapshot, - getActiveRuntimeWebToolsMetadata, - getActiveSecretsRuntimeSnapshot, - prepareSecretsRuntimeSnapshot, -} from "./runtime.js"; - -vi.unmock("../version.js"); - -describe("secrets runtime snapshot web integration", () => { - let envSnapshot: ReturnType; - - beforeEach(() => { - envSnapshot = captureEnv([ - "OPENCLAW_BUNDLED_PLUGINS_DIR", - "OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE", - "OPENCLAW_VERSION", - ]); - delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; - process.env.OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE = "1"; - delete process.env.OPENCLAW_VERSION; - }); - - afterEach(() => { - vi.restoreAllMocks(); - envSnapshot.restore(); - clearSecretsRuntimeSnapshot(); - clearRuntimeConfigSnapshot(); - clearConfigCache(); - }); - - it( - "keeps last-known-good web runtime snapshot when reload introduces unresolved active web refs", - async () => { - await withTempHome("openclaw-secrets-runtime-web-reload-lkg-", async (home) => { - const prepared = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - provider: "gemini", - }, - }, - }, - plugins: { - entries: { - google: { - config: { - webSearch: { - apiKey: { - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - }), - env: { - WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-runtime-key", - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => loadAuthStoreWithProfiles({}), - }); - - activateSecretsRuntimeSnapshot(prepared); - - await expect( - writeConfigFile({ - ...loadConfig(), - plugins: { - entries: { - google: { - config: { - webSearch: { - apiKey: { - source: "env", - provider: "default", - id: "MISSING_WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - tools: { - web: { - search: { - provider: "gemini", - }, - }, - }, - }), - ).rejects.toThrow( - /runtime snapshot refresh failed: .*WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK/i, - ); - - const activeAfterFailure = getActiveSecretsRuntimeSnapshot(); - expect(activeAfterFailure).not.toBeNull(); - const loadedGoogleWebSearchConfig = loadConfig().plugins?.entries?.google?.config as - | { webSearch?: { apiKey?: unknown } } - | undefined; - expect(loadedGoogleWebSearchConfig?.webSearch?.apiKey).toBe( - "web-search-gemini-runtime-key", - ); - const activeSourceGoogleWebSearchConfig = activeAfterFailure?.sourceConfig.plugins?.entries - ?.google?.config as { webSearch?: { apiKey?: unknown } } | undefined; - expect(activeSourceGoogleWebSearchConfig?.webSearch?.apiKey).toEqual({ - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }); - expect(getActiveRuntimeWebToolsMetadata()?.search.selectedProvider).toBe("gemini"); - - const persistedConfig = JSON.parse( - await fs.readFile(path.join(home, ".openclaw", "openclaw.json"), "utf8"), - ) as OpenClawConfig; - const persistedGoogleWebSearchConfig = persistedConfig.plugins?.entries?.google?.config as - | { webSearch?: { apiKey?: unknown } } - | undefined; - expect(persistedGoogleWebSearchConfig?.webSearch?.apiKey).toEqual({ - source: "env", - provider: "default", - id: "MISSING_WEB_SEARCH_GEMINI_API_KEY", - }); - }); - }, - SECRETS_RUNTIME_INTEGRATION_TIMEOUT_MS, - ); -});