mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(plugins): emit actionable install hint for externalized channel plugins (#77502)
Fixes #77483.\n\n- Suggest catalog-backed install commands for missing official external plugins in config validation.\n- Preserve stale/remove wording for non-catalog missing plugins.\n- Add regression coverage for plugins.entries and plugins.allow warnings.\n\nVerification:\n- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/config/validation.ts src/config/config.plugin-validation.test.ts\n- pnpm test src/config/config.plugin-validation.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts\n- pnpm crabbox:run -- --provider blacksmith-testbox ... pnpm check:changed\n- GitHub CI green on d1b1b10444
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.
|
||||
- Dependencies: refresh runtime and provider packages including Pi 0.73.0, ACPX adapters, OpenAI, Anthropic, Slack, and TypeScript native preview, while keeping the Bedrock runtime installer override pinned below the Windows ARM Node 24 npm resolver failure.
|
||||
- Agents/performance: pass the resolved workspace through BTW, compaction, embedded-run model generation, and PDF model setup so explicit agent-dir model refreshes can reuse the current workspace-scoped plugin metadata snapshot instead of falling back to cold plugin metadata scans. (#77519, #77532)
|
||||
- Config/plugin auto-enable: prefer the claiming plugin manifest id over a built-in channel alias when auto-allowlisting a configured channel, so WeCom/Yuanbao-style aliases resolve to the installed plugin id. Thanks @Beandon13.
|
||||
|
||||
@@ -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,
|
||||
@@ -93,6 +97,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;
|
||||
}
|
||||
@@ -1441,6 +1461,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