Skip to content

Commit 7bb69e1

Browse files
committed
fix(config): suggest official plugin installs
1 parent a4f2bf2 commit 7bb69e1

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
1010

1111
### Changes
1212

13+
- Plugins/migration: emit catalog-backed install hints when `plugins.entries` or `plugins.allow` references an official external plugin that is not installed, so upgraded configs point operators to `openclaw plugins install <spec>` instead of telling them to remove valid plugin config. (#77483) Thanks @hclsys.
1314
- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys.
1415
- Models/auth: add `openclaw models auth list [--provider <id>] [--json]` so users can inspect saved per-agent auth profiles without dumping secrets or hitting the old “too many arguments” path. Thanks @vincentkoc.
1516
- Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar.

src/config/config.plugin-validation.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,44 @@ describe("config plugin validation", () => {
253253
}
254254
});
255255

256+
it("reports catalog install hints for missing configured official external plugins", async () => {
257+
const res = validateConfigObjectWithPlugins(
258+
{
259+
agents: { list: [{ id: "pi" }] },
260+
plugins: {
261+
entries: { brave: { enabled: true } },
262+
allow: ["brave"],
263+
},
264+
},
265+
{
266+
env: suiteEnv(),
267+
pluginMetadataSnapshot: {
268+
manifestRegistry: {
269+
plugins: [],
270+
diagnostics: [],
271+
},
272+
},
273+
},
274+
);
275+
276+
expect(res.ok).toBe(true);
277+
const message =
278+
"plugin not installed: brave — install the official external plugin with: openclaw plugins install @openclaw/brave-plugin";
279+
expect(res.warnings ?? []).toEqual(
280+
expect.arrayContaining([
281+
{ path: "plugins.entries.brave", message },
282+
{ path: "plugins.allow", message },
283+
]),
284+
);
285+
expect(
286+
(res.warnings ?? []).some(
287+
(warning) =>
288+
(warning.path === "plugins.entries.brave" || warning.path === "plugins.allow") &&
289+
warning.message.includes("remove it from plugins config"),
290+
),
291+
).toBe(false);
292+
});
293+
256294
it.runIf(process.platform !== "win32")(
257295
"reports configured blocked plugins without stale not-found wording",
258296
async () => {
@@ -493,7 +531,7 @@ describe("config plugin validation", () => {
493531
expect(res.warnings ?? []).toContainEqual({
494532
path: "plugins.allow",
495533
message:
496-
"plugin not found: discord (stale config entry ignored; remove it from plugins config)",
534+
"plugin not installed: discord — install the official external plugin with: openclaw plugins install @openclaw/discord",
497535
});
498536
});
499537

src/config/validation.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import {
1212
import { loadInstalledPluginIndexInstallRecordsSync } from "../plugins/installed-plugin-index-record-reader.js";
1313
import { resolveManifestCommandAliasOwnerInRegistry } from "../plugins/manifest-command-aliases.js";
1414
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
15+
import {
16+
getOfficialExternalPluginCatalogEntry,
17+
resolveOfficialExternalPluginInstall,
18+
} from "../plugins/official-external-plugin-catalog.js";
1519
import {
1620
loadPluginMetadataSnapshot,
1721
type PluginMetadataSnapshot,
@@ -96,6 +100,22 @@ function formatConfigPath(segments: readonly ConfigPathSegment[]): string {
96100
return segments.join(".");
97101
}
98102

103+
function formatMissingOfficialExternalPluginWarning(pluginId: string): string | null {
104+
const catalogEntry = getOfficialExternalPluginCatalogEntry(pluginId);
105+
if (!catalogEntry) {
106+
return null;
107+
}
108+
const install = resolveOfficialExternalPluginInstall(catalogEntry);
109+
const npmSpec = install?.npmSpec?.trim();
110+
const clawhubSpec = install?.clawhubSpec?.trim();
111+
const installSpec =
112+
install?.defaultChoice === "clawhub" ? (clawhubSpec ?? npmSpec) : (npmSpec ?? clawhubSpec);
113+
if (!installSpec) {
114+
return null;
115+
}
116+
return `plugin not installed: ${pluginId} — install the official external plugin with: openclaw plugins install ${installSpec}`;
117+
}
118+
99119
function asJsonSchemaLike(value: unknown): JsonSchemaLike | null {
100120
return value && typeof value === "object" ? (value as JsonSchemaLike) : null;
101121
}
@@ -1496,6 +1516,16 @@ function validateConfigObjectWithPluginsBase(
14961516
}
14971517
return;
14981518
}
1519+
if (opts?.warnOnly) {
1520+
const externalInstallWarning = formatMissingOfficialExternalPluginWarning(pluginId);
1521+
if (externalInstallWarning) {
1522+
warnings.push({
1523+
path,
1524+
message: externalInstallWarning,
1525+
});
1526+
return;
1527+
}
1528+
}
14991529
if (opts?.warnOnly) {
15001530
warnings.push({
15011531
path,

0 commit comments

Comments
 (0)