feat(plugins): externalize DuckDuckGo search (#116740)

This commit is contained in:
Vincent Koc
2026-07-31 18:49:07 +08:00
committed by GitHub
parent a94e1c8d8b
commit 6830820755
11 changed files with 133 additions and 17 deletions
+4 -4
View File
@@ -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.
+1 -1
View File
@@ -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
+6
View File
@@ -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:
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/duckduckgo-plugin
openclaw gateway restart
```
</Step>
<Step title="Configure">
```bash
openclaw configure --section web
+12
View File
@@ -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 <https://docs.openclaw.ai/tools/duckduckgo-search> for setup and configuration.
+1 -1
View File
@@ -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());
},
+23 -3
View File
@@ -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
}
}
}
+1
View File
@@ -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/**",
@@ -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.",
+9 -8
View File
@@ -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 } : {}),
},
]);
},
@@ -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"],
@@ -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();