diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef3db16e54..369e6397eb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai - Plugin hooks: expose first-class run, message, sender, session, and trace correlation fields on message hook contexts and run lifecycle events. Thanks @vincentkoc. - Plugins/setup: include `setup.providers[].envVars` in generic provider auth/env lookups and warn non-bundled plugins that still rely on deprecated `providerAuthEnvVars` compatibility metadata. Thanks @vincentkoc. - Plugins/setup: surface manifest provider auth choices directly in provider setup flow before falling back to setup runtime or install-catalog choices. Thanks @vincentkoc. +- Plugins/setup: warn when descriptor-only setup plugins still ship ignored setup runtime entries, keeping `setup.requiresRuntime: false` semantics explicit without breaking existing metadata. Thanks @vincentkoc. - TUI/dependencies: remove direct `cli-highlight` usage from the OpenClaw TUI code-block renderer, keeping themed code coloring without the extra root dependency. Thanks @vincentkoc. - Diagnostics/OTEL: export run, model-call, and tool-execution diagnostic lifecycle events as OTEL spans without retaining live span state. Thanks @vincentkoc. - Providers/Anthropic Vertex: move the Vertex SDK runtime behind the bundled provider plugin so core no longer owns that provider-specific dependency. Thanks @vincentkoc. diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index cfca2d643fa..2cdf604ec61 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -337,9 +337,11 @@ on `setup.providers[].envVars`. Set `requiresRuntime: false` only when those descriptors are sufficient for the setup surface. OpenClaw treats explicit `false` as a descriptor-only contract -and will not execute `setup-api` for setup lookup. Omitted `requiresRuntime` -keeps legacy fallback behavior so existing plugins that added descriptors -without the flag do not break. +and will not execute `setup-api` or `openclaw.setupEntry` for setup lookup. If +a descriptor-only plugin still ships one of those setup runtime entries, +OpenClaw reports an additive diagnostic and continues ignoring it. Omitted +`requiresRuntime` keeps legacy fallback behavior so existing plugins that added +descriptors without the flag do not break. Because setup lookup can execute plugin-owned `setup-api` code, normalized `setup.providers[].id` and `setup.cliBackends[]` values must stay unique across diff --git a/src/plugins/setup-registry.test.ts b/src/plugins/setup-registry.test.ts index df7adfbd8f8..815fa5e2fac 100644 --- a/src/plugins/setup-registry.test.ts +++ b/src/plugins/setup-registry.test.ts @@ -373,6 +373,42 @@ describe("setup-registry getJiti", () => { expect(resolvePluginSetupProvider({ provider: "openai", env: {} })).toBeUndefined(); expect(resolvePluginSetupCliBackend({ backend: "codex-cli", env: {} })).toBeUndefined(); + expect(resolvePluginSetupRegistry({ env: {} })).toEqual({ + providers: [], + cliBackends: [], + configMigrations: [], + autoEnableProbes: [], + diagnostics: [ + expect.objectContaining({ + pluginId: "openai", + code: "setup-descriptor-runtime-disabled", + }), + ], + }); + expect(mocks.createJiti).not.toHaveBeenCalled(); + }); + + it("does not report descriptor-only diagnostics for bundled setup-api fallback paths", () => { + const parentDir = makeTempDir(); + const pluginRoot = path.join(parentDir, "openai"); + fs.mkdirSync(pluginRoot); + expect(fs.existsSync(path.join(process.cwd(), "extensions", "openai", "setup-api.ts"))).toBe( + true, + ); + mocks.loadPluginManifestRegistry.mockReturnValue({ + plugins: [ + { + id: "workspace-openai", + rootDir: pluginRoot, + setup: { + providers: [{ id: "workspace-openai" }], + requiresRuntime: false, + }, + }, + ], + diagnostics: [], + }); + expect(resolvePluginSetupRegistry({ env: {} })).toEqual({ providers: [], cliBackends: [], diff --git a/src/plugins/setup-registry.ts b/src/plugins/setup-registry.ts index bf5441bf071..4b92185a3d1 100644 --- a/src/plugins/setup-registry.ts +++ b/src/plugins/setup-registry.ts @@ -47,6 +47,7 @@ type SetupAutoEnableProbeEntry = { }; export type PluginSetupRegistryDiagnosticCode = + | "setup-descriptor-runtime-disabled" | "setup-descriptor-provider-missing-runtime" | "setup-descriptor-provider-runtime-undeclared" | "setup-descriptor-cli-backend-missing-runtime" @@ -189,7 +190,10 @@ function buildSetupCliBackendCacheKey(params: { }); } -function resolveSetupApiPath(rootDir: string): string | null { +function resolveSetupApiPath( + rootDir: string, + options?: { includeBundledSourceFallback?: boolean }, +): string | null { const orderedExtensions = RUNNING_FROM_BUILT_ARTIFACT ? SETUP_API_EXTENSIONS : ([...SETUP_API_EXTENSIONS.slice(3), ...SETUP_API_EXTENSIONS.slice(0, 3)] as const); @@ -209,6 +213,10 @@ function resolveSetupApiPath(rootDir: string): string | null { return direct; } + if (options?.includeBundledSourceFallback === false) { + return null; + } + const bundledExtensionDir = path.basename(rootDir); const repoRootCandidates = [path.resolve(path.dirname(CURRENT_MODULE_PATH), "..", "..")]; for (const repoRoot of repoRootCandidates) { @@ -283,6 +291,19 @@ function resolveRegister(mod: OpenClawPluginModule): { return {}; } +function resolveLoadableSetupRuntimeSource(record: PluginManifestRecord): string | null { + return record.setupSource ?? resolveSetupApiPath(record.rootDir); +} + +function resolveDeclaredSetupRuntimeSource(record: PluginManifestRecord): string | null { + return ( + record.setupSource ?? + resolveSetupApiPath(record.rootDir, { + includeBundledSourceFallback: false, + }) + ); +} + function resolveSetupRegistration(record: PluginManifestRecord): { setupSource: string; register: (api: ReturnType) => void | Promise; @@ -290,7 +311,7 @@ function resolveSetupRegistration(record: PluginManifestRecord): { if (record.setup?.requiresRuntime === false) { return null; } - const setupSource = record.setupSource ?? resolveSetupApiPath(record.rootDir); + const setupSource = resolveLoadableSetupRuntimeSource(record); if (!setupSource) { return null; } @@ -399,6 +420,21 @@ function mapNormalizedIds(ids: readonly string[]): Map { return mapped; } +function pushDescriptorRuntimeDisabledDiagnostic(params: { + record: PluginManifestRecord; + diagnostics: PluginSetupRegistryDiagnostic[]; +}): void { + if (!resolveDeclaredSetupRuntimeSource(params.record)) { + return; + } + params.diagnostics.push({ + pluginId: params.record.id, + code: "setup-descriptor-runtime-disabled", + message: + "setup.requiresRuntime is false, so OpenClaw ignored the plugin setup runtime entry. Remove setup-api/openclaw.setupEntry or set requiresRuntime true if setup lookup still needs plugin code.", + }); +} + function pushSetupDescriptorDriftDiagnostics(params: { record: PluginManifestRecord; providers: readonly ProviderPlugin[]; @@ -504,6 +540,13 @@ export function resolvePluginSetupRegistry(params?: { if (selectedPluginIds && !selectedPluginIds.has(record.id)) { continue; } + if (record.setup?.requiresRuntime === false) { + pushDescriptorRuntimeDisabledDiagnostic({ + record, + diagnostics, + }); + continue; + } const setupRegistration = resolveSetupRegistration(record); if (!setupRegistration) { continue;