fix(secrets): resolve plugin env metadata cold

This commit is contained in:
Vincent Koc
2026-04-25 19:18:21 -07:00
parent c14d2b0c1f
commit 6afac5208a
9 changed files with 141 additions and 46 deletions

View File

@@ -33,6 +33,22 @@ vi.mock("../plugins/manifest-registry.js", async (importOriginal) => {
};
});
vi.mock("../plugins/manifest-registry-installed.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/manifest-registry-installed.js")>();
return {
...actual,
loadPluginManifestRegistryForInstalledIndex: loadPluginManifestRegistry,
};
});
vi.mock("../plugins/plugin-registry.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/plugin-registry.js")>();
return {
...actual,
loadPluginRegistrySnapshot: () => ({ plugins: [] }),
};
});
vi.mock("./cli-runner.js", () => ({
runCliAgent: runCliAgentMock,
}));

View File

@@ -61,6 +61,12 @@ vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
resolveManifestContractOwnerPluginId,
}));
vi.mock("../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: loadPluginManifestRegistry,
}));
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: () => ({ plugins: [] }),
}));
vi.mock("../plugins/provider-runtime.js", () => ({
resolveProviderSyntheticAuthWithPlugin,
}));

View File

@@ -1,16 +1,24 @@
import { describe, expect, it, vi } from "vitest";
const loadPluginManifestRegistry = vi.hoisted(() => vi.fn());
const pluginRegistryMocks = vi.hoisted(() => ({
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
vi.mock("../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex:
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
}));
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot,
}));
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
describe("provider auth aliases", () => {
it("treats deprecated auth choice ids as provider auth aliases", () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "openai",

View File

@@ -1,11 +1,12 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import {
isWorkspacePluginAllowedByConfig,
normalizePluginConfigId,
} from "../plugins/plugin-config-trust.js";
import type { PluginOrigin } from "../plugins/plugin-origin.types.js";
import { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
import { normalizeProviderId } from "./provider-id.js";
export type ProviderAuthAliasLookupParams = {
@@ -83,11 +84,18 @@ function setPreferredAlias(params: {
export function resolveProviderAuthAliasMap(
params?: ProviderAuthAliasLookupParams,
): Record<string, string> {
const registry = loadPluginManifestRegistry({
const index = loadPluginRegistrySnapshot({
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
});
const registry = loadPluginManifestRegistryForInstalledIndex({
index,
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
includeDisabled: true,
});
const preferredAliases = new Map<string, ProviderAuthAliasCandidate>();
const aliases: Record<string, string> = Object.create(null) as Record<string, string>;
for (const plugin of registry.plugins) {

View File

@@ -9,23 +9,37 @@ type MockManifestRegistry = {
diagnostics: unknown[];
};
const loadPluginManifestRegistry = vi.hoisted(() =>
vi.fn<() => MockManifestRegistry>(() => ({ plugins: [], diagnostics: [] })),
);
const pluginRegistryMocks = vi.hoisted(() => ({
loadPluginManifestRegistryForInstalledIndex: vi.fn<() => MockManifestRegistry>(() => ({
plugins: [],
diagnostics: [],
})),
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
vi.mock("../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex:
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
}));
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot,
}));
describe("channel env vars dynamic manifest metadata", () => {
beforeEach(() => {
vi.resetModules();
loadPluginManifestRegistry.mockReset();
loadPluginManifestRegistry.mockReturnValue({ plugins: [], diagnostics: [] });
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReset();
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [],
diagnostics: [],
});
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset();
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
});
it("includes later-installed plugin env vars without a bundled generated map", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-mattermost",

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
export { isSafeChannelEnvVarTriggerName } from "./channel-env-var-names.js";
type ChannelEnvVarLookupParams = {
@@ -32,11 +33,18 @@ function appendUniqueEnvVarCandidates(
export function resolveChannelEnvVars(
params?: ChannelEnvVarLookupParams,
): Record<string, readonly string[]> {
const registry = loadPluginManifestRegistry({
const index = loadPluginRegistrySnapshot({
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
});
const registry = loadPluginManifestRegistryForInstalledIndex({
index,
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
includeDisabled: true,
});
const candidates: Record<string, string[]> = {};
for (const plugin of registry.plugins) {
if (!plugin.channelEnvVars) {

View File

@@ -25,23 +25,37 @@ type MockManifestRegistry = {
diagnostics: unknown[];
};
const loadPluginManifestRegistry = vi.hoisted(() =>
vi.fn<() => MockManifestRegistry>(() => ({ plugins: [], diagnostics: [] })),
);
const pluginRegistryMocks = vi.hoisted(() => ({
loadPluginManifestRegistryForInstalledIndex: vi.fn<() => MockManifestRegistry>(() => ({
plugins: [],
diagnostics: [],
})),
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
vi.mock("../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex:
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
}));
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot,
}));
describe("provider env vars dynamic manifest metadata", () => {
beforeEach(() => {
loadPluginManifestRegistry.mockReset();
loadPluginManifestRegistry.mockReturnValue({ plugins: [], diagnostics: [] });
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReset();
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [],
diagnostics: [],
});
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset();
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
__testing.resetProviderEnvVarCachesForTests();
});
it("includes later-installed plugin env vars without a bundled generated map", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-fireworks",
@@ -64,7 +78,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("includes setup provider env vars without loading setup runtime", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-model-studio",
@@ -88,7 +102,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("appends setup provider env vars after explicit provider auth env vars", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-fireworks",
@@ -113,7 +127,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("keeps lazy manifest-backed exports cold until accessed and resolves them once", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-fireworks",
@@ -126,19 +140,22 @@ describe("provider env vars dynamic manifest metadata", () => {
diagnostics: [],
});
expect(loadPluginManifestRegistry).not.toHaveBeenCalled();
expect(pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
expect(PROVIDER_ENV_VARS.fireworks).toEqual(["FIREWORKS_ALT_API_KEY"]);
expect(PROVIDER_AUTH_ENV_VAR_CANDIDATES.fireworks).toEqual(["FIREWORKS_ALT_API_KEY"]);
const initialLoads = loadPluginManifestRegistry.mock.calls.length;
const initialLoads =
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mock.calls.length;
expect(initialLoads).toBeGreaterThan(0);
void PROVIDER_ENV_VARS.fireworks;
void PROVIDER_AUTH_ENV_VAR_CANDIDATES.fireworks;
expect(loadPluginManifestRegistry).toHaveBeenCalledTimes(initialLoads);
expect(pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(
initialLoads,
);
});
it("reuses the lazy default lookup cache for repeated provider env var reads", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "external-fireworks",
@@ -152,14 +169,17 @@ describe("provider env vars dynamic manifest metadata", () => {
});
expect(getProviderEnvVars("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]);
const initialLoads = loadPluginManifestRegistry.mock.calls.length;
const initialLoads =
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mock.calls.length;
expect(initialLoads).toBeGreaterThan(0);
expect(getProviderEnvVars("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]);
expect(loadPluginManifestRegistry).toHaveBeenCalledTimes(initialLoads);
expect(pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(
initialLoads,
);
});
it("keeps workspace plugin env vars in default lookups", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "workspace-audio",
@@ -179,7 +199,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("excludes untrusted workspace plugin env vars when requested", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "workspace-audio",
@@ -229,7 +249,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("keeps explicitly trusted workspace plugin env vars when requested", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "workspace-audio",
@@ -257,7 +277,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("does not trust arbitrary workspace plugin ids from the context engine slot", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "workspace-audio",
@@ -287,7 +307,7 @@ describe("provider env vars dynamic manifest metadata", () => {
});
it("keeps selected workspace context engine env vars when requested", async () => {
loadPluginManifestRegistry.mockReturnValue({
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
{
id: "workspace-engine",

View File

@@ -1,11 +1,12 @@
import { resolveProviderAuthAliasMap } from "../agents/provider-auth-aliases.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import {
isWorkspacePluginAllowedByConfig,
normalizePluginConfigId,
} from "../plugins/plugin-config-trust.js";
import { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
import { hasKind } from "../plugins/slots.js";
const CORE_PROVIDER_AUTH_ENV_VAR_CANDIDATES = {
@@ -76,11 +77,18 @@ function appendUniqueEnvVarCandidates(
function resolveManifestProviderAuthEnvVarCandidates(
params?: ProviderEnvVarLookupParams,
): Record<string, string[]> {
const registry = loadPluginManifestRegistry({
const index = loadPluginRegistrySnapshot({
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
});
const registry = loadPluginManifestRegistryForInstalledIndex({
index,
config: params?.config,
workspaceDir: params?.workspaceDir,
env: params?.env,
includeDisabled: true,
});
const candidates: Record<string, string[]> = {};
for (const plugin of registry.plugins) {
if (!shouldUsePluginProviderEnvVars(plugin, params)) {

View File

@@ -1,7 +1,6 @@
import {
loadPluginManifestRegistry,
type PluginManifestRecord,
} from "../plugins/manifest-registry.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js";
import type { SecretTargetRegistryEntry } from "./target-registry-types.js";
@@ -51,7 +50,11 @@ function hasWebProviderContract(
function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
const entries: SecretTargetRegistryEntry[] = [];
for (const record of loadPluginManifestRegistry({}).plugins) {
const index = loadPluginRegistrySnapshot({});
for (const record of loadPluginManifestRegistryForInstalledIndex({
index,
includeDisabled: true,
}).plugins) {
if (record.origin !== "bundled") {
continue;
}
@@ -70,7 +73,11 @@ function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegist
function listChannelSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
const entries: SecretTargetRegistryEntry[] = [];
for (const record of loadPluginManifestRegistry({}).plugins) {
const index = loadPluginRegistrySnapshot({});
for (const record of loadPluginManifestRegistryForInstalledIndex({
index,
includeDisabled: true,
}).plugins) {
if (record.origin !== "bundled") {
continue;
}