diff --git a/docs/reference/templates/BOOTSTRAP.md b/docs/reference/templates/BOOTSTRAP.md index ba60f7434b58..e31cb45b0135 100644 --- a/docs/reference/templates/BOOTSTRAP.md +++ b/docs/reference/templates/BOOTSTRAP.md @@ -11,6 +11,12 @@ _You just woke up. Keep this first conversation short and make it yours._ OpenClaw only seeds this file into a brand-new workspace, alongside `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, and `USER.md`. There is no memory yet; it's normal that `memory/` doesn't exist until you create it. +**The user's request always comes first.** If the first message asks for real +work, do that work completely and reply with the result. Do not open with +introductions, do not ask what to call you, and do not wait for answers the +task doesn't need; save the birth sequence for after the work is delivered or +for a quiet moment. This file is a ritual, not a gate. + Complete these three beats. Do not turn them into a questionnaire or a long biography. diff --git a/src/plugins/web-provider-runtime-shared.test.ts b/src/plugins/web-provider-runtime-shared.test.ts index 0910bbd92de5..b098569185e4 100644 --- a/src/plugins/web-provider-runtime-shared.test.ts +++ b/src/plugins/web-provider-runtime-shared.test.ts @@ -455,6 +455,46 @@ describe("web-provider-runtime-shared", () => { expect(mocks.loadOpenClawPlugins).not.toHaveBeenCalled(); }); + it("does not treat an active registry missing declared candidates as authoritative", () => { + // Regression: an active registry with SOME web providers used to win even when a + // manifest-declared candidate (npm-installed Brave with BRAVE_API_KEY set) was + // absent from it, so env-var auto-detect could never see the installed provider. + const activeRegistry = { source: "active" }; + const scopedRegistry = { source: "scoped" }; + const mapRegistryProviders = vi.fn(({ registry }) => + registry === scopedRegistry ? ["brave", "grok"] : ["grok"], + ); + mocks.getLoadedRuntimePluginRegistry.mockImplementation((args: unknown) => { + const requiredPluginIds = (args as { requiredPluginIds?: readonly string[] }) + ?.requiredPluginIds; + // Simulate active-registry coverage: brave never loaded at startup. + if (requiredPluginIds?.includes("brave")) { + return undefined; + } + return activeRegistry as never; + }); + mocks.loadOpenClawPlugins.mockReturnValue(scopedRegistry as never); + + const result = resolveRuntimeWebProviders( + { + config: {}, + env: { BRAVE_API_KEY: "key" } as never, + }, + { + resolveBundledResolutionConfig: () => ({ + config: {}, + activationSourceConfig: {}, + autoEnabledReasons: {}, + }), + resolveCandidatePluginIds: () => ["brave", "xai"], + mapRegistryProviders, + }, + ); + + expect(result).toEqual(["brave", "grok"]); + expect(mocks.loadOpenClawPlugins).toHaveBeenCalledTimes(1); + }); + it("falls back when the direct runtime registry has no web providers", () => { const activeRegistry = { source: "active" }; const fallbackRegistry = { source: "fallback" }; diff --git a/src/plugins/web-provider-runtime-shared.ts b/src/plugins/web-provider-runtime-shared.ts index 370e56eb8a46..9d3ddbc8ad5d 100644 --- a/src/plugins/web-provider-runtime-shared.ts +++ b/src/plugins/web-provider-runtime-shared.ts @@ -270,21 +270,11 @@ export function resolveRuntimeWebProviders( params: Omit, deps: ResolveWebProviderRuntimeDeps, ): TEntry[] { - const runtimeRegistry = getLoadedRuntimePluginRegistry({ - env: params.env, - workspaceDir: params.workspaceDir, - requiredPluginIds: params.onlyPluginIds, - }); - const hasExplicitEmptyScope = - params.onlyPluginIds !== undefined && params.onlyPluginIds.length === 0; - const runtimeProviders = resolveRuntimeRegistryWebProviders({ - hasExplicitEmptyScope, - mapRegistryProviders: deps.mapRegistryProviders, - onlyPluginIds: params.onlyPluginIds, - registry: runtimeRegistry, - }); - if (runtimeProviders?.shouldReturn) { - return runtimeProviders.providers; - } + // Do not treat the active registry's provider set as authoritative here: it can + // be non-empty while still missing manifest-declared candidates that never load + // at startup (for example an npm-installed Brave plugin with BRAVE_API_KEY set, + // whose manifest uses activation.onStartup=false). resolvePluginWebProviders + // reuses the active registry only when it covers every declared candidate, and + // otherwise runs the same scoped load the explicitly-configured path uses. return resolvePluginWebProviders(params, deps); }