mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 21:10:43 +00:00
fix(config): suggest official plugin installs
This commit is contained in:
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -253,6 +253,44 @@ describe("config plugin validation", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reports catalog install hints for missing configured official external plugins", async () => {
|
||||
const res = validateConfigObjectWithPlugins(
|
||||
{
|
||||
agents: { list: [{ id: "pi" }] },
|
||||
plugins: {
|
||||
entries: { brave: { enabled: true } },
|
||||
allow: ["brave"],
|
||||
},
|
||||
},
|
||||
{
|
||||
env: suiteEnv(),
|
||||
pluginMetadataSnapshot: {
|
||||
manifestRegistry: {
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
const message =
|
||||
"plugin not installed: brave — install the official external plugin with: openclaw plugins install @openclaw/brave-plugin";
|
||||
expect(res.warnings ?? []).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ path: "plugins.entries.brave", message },
|
||||
{ path: "plugins.allow", message },
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
(res.warnings ?? []).some(
|
||||
(warning) =>
|
||||
(warning.path === "plugins.entries.brave" || warning.path === "plugins.allow") &&
|
||||
warning.message.includes("remove it from plugins config"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"reports configured blocked plugins without stale not-found wording",
|
||||
async () => {
|
||||
@@ -493,7 +531,7 @@ describe("config plugin validation", () => {
|
||||
expect(res.warnings ?? []).toContainEqual({
|
||||
path: "plugins.allow",
|
||||
message:
|
||||
"plugin not found: discord (stale config entry ignored; remove it from plugins config)",
|
||||
"plugin not installed: discord — install the official external plugin with: openclaw plugins install @openclaw/discord",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
import { loadInstalledPluginIndexInstallRecordsSync } from "../plugins/installed-plugin-index-record-reader.js";
|
||||
import { resolveManifestCommandAliasOwnerInRegistry } from "../plugins/manifest-command-aliases.js";
|
||||
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import {
|
||||
getOfficialExternalPluginCatalogEntry,
|
||||
resolveOfficialExternalPluginInstall,
|
||||
} from "../plugins/official-external-plugin-catalog.js";
|
||||
import {
|
||||
loadPluginMetadataSnapshot,
|
||||
type PluginMetadataSnapshot,
|
||||
@@ -96,6 +100,22 @@ function formatConfigPath(segments: readonly ConfigPathSegment[]): string {
|
||||
return segments.join(".");
|
||||
}
|
||||
|
||||
function formatMissingOfficialExternalPluginWarning(pluginId: string): string | null {
|
||||
const catalogEntry = getOfficialExternalPluginCatalogEntry(pluginId);
|
||||
if (!catalogEntry) {
|
||||
return null;
|
||||
}
|
||||
const install = resolveOfficialExternalPluginInstall(catalogEntry);
|
||||
const npmSpec = install?.npmSpec?.trim();
|
||||
const clawhubSpec = install?.clawhubSpec?.trim();
|
||||
const installSpec =
|
||||
install?.defaultChoice === "clawhub" ? (clawhubSpec ?? npmSpec) : (npmSpec ?? clawhubSpec);
|
||||
if (!installSpec) {
|
||||
return null;
|
||||
}
|
||||
return `plugin not installed: ${pluginId} — install the official external plugin with: openclaw plugins install ${installSpec}`;
|
||||
}
|
||||
|
||||
function asJsonSchemaLike(value: unknown): JsonSchemaLike | null {
|
||||
return value && typeof value === "object" ? (value as JsonSchemaLike) : null;
|
||||
}
|
||||
@@ -1496,6 +1516,16 @@ function validateConfigObjectWithPluginsBase(
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (opts?.warnOnly) {
|
||||
const externalInstallWarning = formatMissingOfficialExternalPluginWarning(pluginId);
|
||||
if (externalInstallWarning) {
|
||||
warnings.push({
|
||||
path,
|
||||
message: externalInstallWarning,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (opts?.warnOnly) {
|
||||
warnings.push({
|
||||
path,
|
||||
|
||||
Reference in New Issue
Block a user