diff --git a/docs/plugins/plugin-inventory.md b/docs/plugins/plugin-inventory.md index 8ceb6ceaab9d..770d5aaf7ba6 100644 --- a/docs/plugins/plugin-inventory.md +++ b/docs/plugins/plugin-inventory.md @@ -51,7 +51,7 @@ Each entry lists the package, distribution route, and description. ## Core npm package -71 plugins +70 plugins - **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint. @@ -87,8 +87,6 @@ Each entry lists the package, distribution route, and description. - **[document-extract](/plugins/reference/document-extract)** (`@openclaw/document-extract-plugin`) - included in OpenClaw. Extract text and fallback page images from local document attachments. -- **[duckduckgo](/plugins/reference/duckduckgo)** (`@openclaw/duckduckgo-plugin`) - included in OpenClaw. Adds web search provider support. - - **[elevenlabs](/plugins/reference/elevenlabs)** (`@openclaw/elevenlabs-speech`) - included in OpenClaw. Adds media understanding provider support. Adds realtime transcription provider support. Adds text-to-speech provider support. - **[fal](/plugins/reference/fal)** (`@openclaw/fal-provider`) - included in OpenClaw. Adds fal model provider support to OpenClaw. @@ -197,7 +195,7 @@ Each entry lists the package, distribution route, and description. ## Official external packages -74 plugins +75 plugins - **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management. @@ -241,6 +239,8 @@ Each entry lists the package, distribution route, and description. - **[discord](/plugins/reference/discord)** (`@openclaw/discord`) - npm; ClawHub. OpenClaw Discord channel plugin for channels, DMs, commands, and app events. +- **[duckduckgo](/plugins/reference/duckduckgo)** (`@openclaw/duckduckgo-plugin`) - npm; ClawHub: `clawhub:@openclaw/duckduckgo-plugin`. Adds web search provider support. + - **[exa](/plugins/reference/exa)** (`@openclaw/exa-plugin`) - npm; ClawHub: `clawhub:@openclaw/exa-plugin`. Adds web search provider support. - **[featherless](/plugins/reference/featherless)** (`@openclaw/featherless-provider`) - npm; ClawHub: `clawhub:@openclaw/featherless-provider`. OpenClaw Featherless AI provider plugin. diff --git a/docs/plugins/reference/duckduckgo.md b/docs/plugins/reference/duckduckgo.md index 43769cbc1106..a74ffe70265d 100644 --- a/docs/plugins/reference/duckduckgo.md +++ b/docs/plugins/reference/duckduckgo.md @@ -12,7 +12,7 @@ Adds web search provider support. ## Distribution - Package: `@openclaw/duckduckgo-plugin` -- Install route: included in OpenClaw +- Install route: npm; ClawHub: `clawhub:@openclaw/duckduckgo-plugin` ## Surface diff --git a/docs/tools/duckduckgo-search.md b/docs/tools/duckduckgo-search.md index 09a81a242614..35a1182f19af 100644 --- a/docs/tools/duckduckgo-search.md +++ b/docs/tools/duckduckgo-search.md @@ -18,6 +18,12 @@ OpenClaw supports DuckDuckGo as a **key-free** `web_search` provider. No API key DuckDuckGo is never auto-selected, since auto-detection only considers providers with usable credentials. Set it explicitly: + + ```bash + openclaw plugins install @openclaw/duckduckgo-plugin + openclaw gateway restart + ``` + ```bash openclaw configure --section web diff --git a/extensions/duckduckgo/README.md b/extensions/duckduckgo/README.md new file mode 100644 index 000000000000..69c1e6fc290f --- /dev/null +++ b/extensions/duckduckgo/README.md @@ -0,0 +1,12 @@ +# OpenClaw DuckDuckGo Plugin + +Official OpenClaw plugin for DuckDuckGo web search. + +Install from OpenClaw: + +```bash +openclaw plugins install @openclaw/duckduckgo-plugin +openclaw gateway restart +``` + +See for setup and configuration. diff --git a/extensions/duckduckgo/index.ts b/extensions/duckduckgo/index.ts index dd9c7de695ab..faeb9c8f4fe8 100644 --- a/extensions/duckduckgo/index.ts +++ b/extensions/duckduckgo/index.ts @@ -5,7 +5,7 @@ import { createDuckDuckGoWebSearchProvider } from "./src/ddg-search-provider.js" export default definePluginEntry({ id: "duckduckgo", name: "DuckDuckGo Plugin", - description: "Bundled DuckDuckGo web search plugin", + description: "Official DuckDuckGo web search plugin", register(api) { api.registerWebSearchProvider(createDuckDuckGoWebSearchProvider()); }, diff --git a/extensions/duckduckgo/package.json b/extensions/duckduckgo/package.json index 93e43bf55757..30f0f97f1d5d 100644 --- a/extensions/duckduckgo/package.json +++ b/extensions/duckduckgo/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/duckduckgo-plugin", "version": "2026.7.2-beta.6", - "private": true, - "description": "OpenClaw DuckDuckGo plugin", + "description": "OpenClaw DuckDuckGo plugin.", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" @@ -10,6 +13,23 @@ "openclaw": { "extensions": [ "./index.ts" - ] + ], + "install": { + "clawhubSpec": "clawhub:@openclaw/duckduckgo-plugin", + "npmSpec": "@openclaw/duckduckgo-plugin", + "defaultChoice": "npm", + "minHostVersion": ">=2026.7.2" + }, + "compat": { + "pluginApi": ">=2026.7.2" + }, + "build": { + "openclawVersion": "2026.7.2", + "bundledDist": false + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true + } } } diff --git a/package.json b/package.json index afcb9633961f..ebe6bd6ed1f7 100644 --- a/package.json +++ b/package.json @@ -260,6 +260,7 @@ "!dist/extensions/diffs/**", "!dist/extensions/diffs-language-pack/**", "!dist/extensions/discord/**", + "!dist/extensions/duckduckgo/**", "!dist/extensions/exa/**", "!dist/extensions/feishu/**", "!dist/extensions/featherless/**", diff --git a/scripts/lib/official-external-plugin-catalog.json b/scripts/lib/official-external-plugin-catalog.json index dbd78887bd8f..37927dbf579d 100644 --- a/scripts/lib/official-external-plugin-catalog.json +++ b/scripts/lib/official-external-plugin-catalog.json @@ -169,6 +169,46 @@ } } }, + { + "name": "@openclaw/duckduckgo-plugin", + "description": "OpenClaw DuckDuckGo plugin.", + "source": "official", + "kind": "plugin", + "openclaw": { + "plugin": { + "id": "duckduckgo", + "label": "DuckDuckGo" + }, + "contracts": { + "webSearchProviders": [ + "duckduckgo" + ] + }, + "webSearchProviders": [ + { + "id": "duckduckgo", + "label": "DuckDuckGo Search (experimental)", + "hint": "Free web search fallback with no API key required", + "onboardingScopes": [ + "text-inference" + ], + "requiresCredential": false, + "envVars": [], + "placeholder": "(no key needed)", + "signupUrl": "https://duckduckgo.com/", + "docsUrl": "https://docs.openclaw.ai/tools/duckduckgo-search", + "credentialPath": "", + "autoDetectOrder": 100 + } + ], + "install": { + "clawhubSpec": "clawhub:@openclaw/duckduckgo-plugin", + "npmSpec": "@openclaw/duckduckgo-plugin", + "defaultChoice": "npm", + "minHostVersion": ">=2026.7.2" + } + } + }, { "name": "@openclaw/exa-plugin", "description": "OpenClaw Exa plugin.", diff --git a/src/cli/plugins-location-bridges.test.ts b/src/cli/plugins-location-bridges.test.ts index ceeea918cee7..67e294c2b647 100644 --- a/src/cli/plugins-location-bridges.test.ts +++ b/src/cli/plugins-location-bridges.test.ts @@ -164,6 +164,37 @@ describe("listPersistedBundledPluginLocationBridges", () => { ]); }); + it("externalizes the shipped bundled DuckDuckGo plugin using official install metadata", async () => { + const pluginId = "duckduckgo"; + const npmSpec = "@openclaw/duckduckgo-plugin"; + readPersistedInstalledPluginIndexMock.mockResolvedValue( + makeIndex({ + pluginId, + manifestPath: `/app/dist/extensions/${pluginId}/openclaw.plugin.json`, + manifestHash: "hash", + source: `/app/dist/extensions/${pluginId}/index.js`, + rootDir: `/app/dist/extensions/${pluginId}`, + origin: "bundled", + enabled: true, + startup: startupInfo, + compat: [], + packageInstall: { + warnings: [], + }, + }), + ); + loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue(makeRegistry(pluginId, [])); + + await expect(listPersistedBundledPluginLocationBridges({})).resolves.toEqual([ + { + bundledPluginId: pluginId, + pluginId, + preferredSource: "npm", + npmSpec, + clawhubSpec: `clawhub:${npmSpec}`, + }, + ]); + }); it("does not create a relocation bridge without persisted or official install metadata", async () => { readPersistedInstalledPluginIndexMock.mockResolvedValue( makeIndex({ diff --git a/src/plugins/official-external-plugin-catalog.test.ts b/src/plugins/official-external-plugin-catalog.test.ts index 7e3d5e9600d4..368dd0b473e2 100644 --- a/src/plugins/official-external-plugin-catalog.test.ts +++ b/src/plugins/official-external-plugin-catalog.test.ts @@ -1960,6 +1960,34 @@ describe("official external plugin catalog", () => { }); }); + it("preserves DuckDuckGo's keyless web search setup contract", () => { + const duckduckgo = expectCatalogEntry("duckduckgo"); + const manifest = getOfficialExternalPluginCatalogManifest(duckduckgo); + + expect(resolveOfficialExternalPluginInstall(duckduckgo)).toEqual({ + clawhubSpec: "clawhub:@openclaw/duckduckgo-plugin", + npmSpec: "@openclaw/duckduckgo-plugin", + defaultChoice: "npm", + minHostVersion: ">=2026.7.2", + }); + expect(manifest?.contracts?.webSearchProviders).toEqual(["duckduckgo"]); + expect(manifest?.webSearchProviders).toEqual([ + { + id: "duckduckgo", + label: "DuckDuckGo Search (experimental)", + hint: "Free web search fallback with no API key required", + onboardingScopes: ["text-inference"], + requiresCredential: false, + envVars: [], + placeholder: "(no key needed)", + signupUrl: "https://duckduckgo.com/", + docsUrl: "https://docs.openclaw.ai/tools/duckduckgo-search", + credentialPath: "", + autoDetectOrder: 100, + }, + ]); + }); + it("lists LongCat as an official external provider", () => { const longcat = expectCatalogEntry("longcat"); diff --git a/test/scripts/bundled-plugin-build-entries.test.ts b/test/scripts/bundled-plugin-build-entries.test.ts index ae25a16272b5..b2dd4505ad1f 100644 --- a/test/scripts/bundled-plugin-build-entries.test.ts +++ b/test/scripts/bundled-plugin-build-entries.test.ts @@ -347,6 +347,14 @@ describe("bundled plugin build entries", () => { expect(artifacts).toContain("dist/extensions/cohere/package.json"); }); + it("excludes the externalized DuckDuckGo plugin from bundled artifacts", () => { + const artifacts = listBundledPluginPackArtifacts(); + + expect(artifacts).not.toContain("dist/extensions/duckduckgo/index.js"); + expect(artifacts).not.toContain("dist/extensions/duckduckgo/openclaw.plugin.json"); + expect(artifacts).not.toContain("dist/extensions/duckduckgo/package.json"); + }); + it("keeps bundled channel secret contracts on packed top-level sidecars", () => { const artifacts = listBundledPluginPackArtifacts(); const excludedPackageDirs = collectRootPackageExcludedExtensionDirs();