diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1c9842e6d..5e0aa386153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Gradium: add a bundled text-to-speech provider with voice-note and telephony output support. (#64958) Thanks @LaurentMazare. +- Plugins/setup: honor explicit `setup.requiresRuntime: false` as a descriptor-only setup contract while keeping omitted values on the legacy setup-api fallback path. 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. - Plugins/activation: expose activation plan reasons and a richer plan API so callers can inspect why a plugin was selected while preserving existing id-list activation behavior. (#70943) Thanks @vincentkoc. diff --git a/docs/plugins/architecture-internals.md b/docs/plugins/architecture-internals.md index ec52352002f..ba1f4c94d92 100644 --- a/docs/plugins/architecture-internals.md +++ b/docs/plugins/architecture-internals.md @@ -71,10 +71,12 @@ or fallback behavior without changing runtime loading semantics. Setup discovery now prefers descriptor-owned ids such as `setup.providers` and `setup.cliBackends` to narrow candidate plugins before it falls back to -`setup-api` for plugins that still need setup-time runtime hooks. If more than -one discovered plugin claims the same normalized setup provider or CLI backend -id, setup lookup refuses the ambiguous owner instead of relying on discovery -order. +`setup-api` for plugins that still need setup-time runtime hooks. Explicit +`setup.requiresRuntime: false` is a descriptor-only cutoff; omitted +`requiresRuntime` keeps the legacy setup-api fallback for compatibility. If more +than one discovered plugin claims the same normalized setup provider or CLI +backend id, setup lookup refuses the ambiguous owner instead of relying on +discovery order. ### What the loader caches diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index 8c2050d6faf..fbd3712b851 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -327,6 +327,12 @@ narrows the candidate plugin and setup still needs richer setup-time runtime hooks, set `requiresRuntime: true` and keep `setup-api` in place as the fallback execution path. +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. + Because setup lookup can execute plugin-owned `setup-api` code, normalized `setup.providers[].id` and `setup.cliBackends[]` values must stay unique across discovered plugins. Ambiguous ownership fails closed instead of picking a diff --git a/src/plugins/setup-registry.test.ts b/src/plugins/setup-registry.test.ts index 91dfd07bd08..7d20fc1c00a 100644 --- a/src/plugins/setup-registry.test.ts +++ b/src/plugins/setup-registry.test.ts @@ -349,6 +349,39 @@ describe("setup-registry getJiti", () => { expect(mocks.createJiti.mock.calls[0]?.[0]).toBe(path.join(pluginRoot, "setup-api.js")); }); + it("treats explicit descriptor-only setup as a runtime cutoff", () => { + const pluginRoot = makeTempDir(); + fs.writeFileSync( + path.join(pluginRoot, "setup-api.js"), + "export default { register(api) { api.registerProvider({ id: 'openai', label: 'OpenAI', auth: [] }); api.registerCliBackend({ id: 'codex-cli', config: { command: 'codex' } }); } };\n", + "utf-8", + ); + mocks.loadPluginManifestRegistry.mockReturnValue({ + plugins: [ + { + id: "openai", + rootDir: pluginRoot, + setup: { + providers: [{ id: "openai" }], + cliBackends: ["codex-cli"], + requiresRuntime: false, + }, + }, + ], + diagnostics: [], + }); + + expect(resolvePluginSetupProvider({ provider: "openai", env: {} })).toBeUndefined(); + expect(resolvePluginSetupCliBackend({ backend: "codex-cli", env: {} })).toBeUndefined(); + expect(resolvePluginSetupRegistry({ env: {} })).toEqual({ + providers: [], + cliBackends: [], + configMigrations: [], + autoEnableProbes: [], + }); + expect(mocks.createJiti).not.toHaveBeenCalled(); + }); + it("does not load setup-api modules from the current working directory", () => { const pluginRoot = makeTempDir(); const workspaceRoot = makeTempDir(); diff --git a/src/plugins/setup-registry.ts b/src/plugins/setup-registry.ts index cd845911fda..b1d9645af89 100644 --- a/src/plugins/setup-registry.ts +++ b/src/plugins/setup-registry.ts @@ -272,6 +272,9 @@ function resolveSetupRegistration(record: PluginManifestRecord): { setupSource: string; register: (api: ReturnType) => void | Promise; } | null { + if (record.setup?.requiresRuntime === false) { + return null; + } const setupSource = record.setupSource ?? resolveSetupApiPath(record.rootDir); if (!setupSource) { return null;