diff --git a/docs/plugins/plugin-inventory.md b/docs/plugins/plugin-inventory.md index d79d99948bae..2492b9334048 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 -66 plugins +65 plugins - **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint. @@ -85,8 +85,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. @@ -187,7 +185,7 @@ Each entry lists the package, distribution route, and description. ## Official external packages -79 plugins +80 plugins - **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management. @@ -233,6 +231,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 815446daf91e..e6cfe81f0f60 100644 --- a/extensions/duckduckgo/package.json +++ b/extensions/duckduckgo/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/duckduckgo-plugin", "version": "2026.7.2", - "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 1fd62961b564..e53ba0b2a464 100644 --- a/package.json +++ b/package.json @@ -261,6 +261,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 ba03884d00c9..05663e8b269a 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 d76637285bfc..106bc2d3cfb9 100644 --- a/src/cli/plugins-location-bridges.test.ts +++ b/src/cli/plugins-location-bridges.test.ts @@ -165,12 +165,13 @@ describe("listPersistedBundledPluginLocationBridges", () => { }); it.each([ - ["synthetic", "@openclaw/synthetic-provider"], - ["teams-meetings", "@openclaw/teams-meetings"], - ["zoom-meetings", "@openclaw/zoom-meetings"], - ])( - "externalizes the shipped bundled %s plugin while preserving default enablement", - async (pluginId, npmSpec) => { + ["duckduckgo", "@openclaw/duckduckgo-plugin", false], + ["synthetic", "@openclaw/synthetic-provider", true], + ["teams-meetings", "@openclaw/teams-meetings", true], + ["zoom-meetings", "@openclaw/zoom-meetings", true], + ] as const)( + "externalizes the shipped bundled %s plugin using official install metadata", + async (pluginId, npmSpec, enabledByDefault) => { readPersistedInstalledPluginIndexMock.mockResolvedValue( makeIndex({ pluginId, @@ -180,7 +181,7 @@ describe("listPersistedBundledPluginLocationBridges", () => { rootDir: `/app/dist/extensions/${pluginId}`, origin: "bundled", enabled: true, - enabledByDefault: true, + ...(enabledByDefault ? { enabledByDefault: true } : {}), startup: startupInfo, compat: [], packageInstall: { @@ -197,7 +198,7 @@ describe("listPersistedBundledPluginLocationBridges", () => { preferredSource: "npm", npmSpec, clawhubSpec: `clawhub:${npmSpec}`, - enabledByDefault: true, + ...(enabledByDefault ? { enabledByDefault: true } : {}), }, ]); }, diff --git a/src/plugins/official-external-plugin-catalog.test.ts b/src/plugins/official-external-plugin-catalog.test.ts index 72c2c481ba13..f19de92439d3 100644 --- a/src/plugins/official-external-plugin-catalog.test.ts +++ b/src/plugins/official-external-plugin-catalog.test.ts @@ -1972,6 +1972,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.each([ ["teams-meetings", "@openclaw/teams-meetings", "teams_meetings", "teams"], ["zoom-meetings", "@openclaw/zoom-meetings", "zoom_meetings", "zoom"], diff --git a/test/scripts/bundled-plugin-build-entries.test.ts b/test/scripts/bundled-plugin-build-entries.test.ts index bb18e4c1531c..84df1bcc921c 100644 --- a/test/scripts/bundled-plugin-build-entries.test.ts +++ b/test/scripts/bundled-plugin-build-entries.test.ts @@ -367,6 +367,14 @@ describe("bundled plugin build entries", () => { expectNoPrefixMatches(artifacts, "dist/extensions/synthetic/"); }); + 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();