diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b85a0515b..1b85d76d697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Plugins: discover provider plugins from `setup.providers[].envVars` credentials during provider discovery while keeping the deprecated `providerAuthEnvVars` fallback. (#81542) Thanks @JARVIS-Glasses. - Docs/Codex harness: clarify that per-agent `CODEX_HOME` isolates `~/.codex` while inherited `HOME` intentionally keeps `.agents` discovery and subprocess user-home state available. - CLI tables: preserve muted/color styling on wrapped continuation lines after multiline cells, keeping `openclaw plugins list` descriptions readable. - iOS: restore first-use Contacts, Calendar, and Reminders permission prompts and add Privacy & Access status/actions in Settings. Thanks @BunsDev. diff --git a/src/plugins/provider-discovery.runtime.test.ts b/src/plugins/provider-discovery.runtime.test.ts index 01cb72b9bb0..040b5219aa6 100644 --- a/src/plugins/provider-discovery.runtime.test.ts +++ b/src/plugins/provider-discovery.runtime.test.ts @@ -51,12 +51,14 @@ function createManifestPlugin(id: string): PluginManifestRecord { function createManifestPluginWithoutDiscovery(params: { id: string; providerAuthEnvVars?: Record; + setupProviders?: NonNullable["providers"]; }): PluginManifestRecord { const { providerDiscoverySource: _providerDiscoverySource, ...plugin } = createManifestPlugin( params.id, ); return { ...plugin, + ...(params.setupProviders ? { setup: { providers: params.setupProviders } } : {}), ...(params.providerAuthEnvVars ? { providerAuthEnvVars: params.providerAuthEnvVars } : {}), }; } @@ -184,6 +186,36 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => { expect(params.onlyPluginIds).toEqual(["deepseek", "kilocode"]); }); + it("falls back to full provider plugins when setup provider env vars are configured", () => { + const codexEntryProvider = createProvider({ id: "codex", mode: "catalog" }); + const fullProviders = [createProvider({ id: "kilocode", mode: "catalog" })]; + mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["codex", "kilocode"]); + mocks.loadPluginMetadataSnapshot.mockReturnValue({ + index: { plugins: [] }, + manifestRegistry: { + plugins: [ + createManifestPlugin("codex"), + createManifestPluginWithoutDiscovery({ + id: "kilocode", + setupProviders: [{ id: "kilocode", envVars: ["KILOCODE_API_KEY"] }], + }), + ], + diagnostics: [], + }, + }); + mocks.loadSource.mockReturnValue(codexEntryProvider); + mocks.resolvePluginProviders.mockReturnValue(fullProviders); + + expect( + resolvePluginDiscoveryProvidersRuntime({ + env: { KILOCODE_API_KEY: "sk-test" } as NodeJS.ProcessEnv, + }), + ).toEqual([{ ...codexEntryProvider, pluginId: "codex" }, ...fullProviders]); + expect(mocks.resolvePluginProviders).toHaveBeenCalledTimes(1); + const params = requireResolvePluginProvidersParams(); + expect(params.onlyPluginIds).toEqual(["kilocode"]); + }); + it("shares one metadata snapshot between provider id discovery and entry loading", () => { const registry = { plugins: [] }; const manifestRegistry = { diff --git a/src/plugins/provider-discovery.runtime.ts b/src/plugins/provider-discovery.runtime.ts index 6be8f5ded04..a6ea0e4b535 100644 --- a/src/plugins/provider-discovery.runtime.ts +++ b/src/plugins/provider-discovery.runtime.ts @@ -56,12 +56,14 @@ function hasProviderAuthEnvCredential( plugin: PluginManifestRecord, env: NodeJS.ProcessEnv, ): boolean { - return Object.values(plugin.providerAuthEnvVars ?? {}).some((envVars) => - envVars.some((name) => { - const value = env[name]?.trim(); - return value !== undefined && value !== ""; - }), - ); + const envVars = [ + ...(plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []), + ...Object.values(plugin.providerAuthEnvVars ?? {}).flat(), + ]; + return envVars.some((name) => { + const value = env[name]?.trim(); + return value !== undefined && value !== ""; + }); } function dedupeSorted(values: Iterable): string[] {