mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-25 02:03:04 +00:00
fix(plugins): surface configured runtime plugin doctor warnings (#81674)
Fixes #81326. Summary: - Warn from `openclaw plugins doctor` when configured runtime owner plugins are missing, disabled, or blocked. - Share configured-runtime plugin install mapping with `openclaw doctor --fix`, including ACP/acpx. - Keep implicit OpenAI Codex preferences quiet to avoid false-positive plugin doctor warnings. Verification: - `pnpm test src/agents/harness-runtimes.test.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts -- --reporter=verbose` - `pnpm exec oxfmt --check CHANGELOG.md src/agents/harness-runtimes.ts src/agents/harness-runtimes.test.ts src/cli/plugins-cli.runtime.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/configured-runtime-plugin-installs.ts src/commands/doctor/shared/missing-configured-plugin-install.ts` - `pnpm build:plugin-sdk:dts` - `codex-review --mode branch` - Testbox-through-Crabbox `pnpm check:changed`: provider `blacksmith-testbox`, id `tbx_01krt8vte22m7ht6wfss4jkeaa`, Actions run https://github.com/openclaw/openclaw/actions/runs/25983150787, exit 0 Co-authored-by: Zavian Wang <36817799+Zavianx@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
collectConfiguredRuntimePluginIds,
|
||||
resolveConfiguredRuntimePluginInstallCandidate,
|
||||
} from "../commands/doctor/shared/configured-runtime-plugin-installs.js";
|
||||
import {
|
||||
assertConfigWriteAllowedInCurrentMode,
|
||||
getRuntimeConfig,
|
||||
@@ -67,6 +71,99 @@ function isErroredConfigSelectedShadowDiagnostic(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function formatConfiguredRuntimePluginInstallSpec(params: {
|
||||
clawhubSpec?: string;
|
||||
defaultChoice?: string;
|
||||
npmSpec?: string;
|
||||
pluginId: string;
|
||||
}): string {
|
||||
const clawhubSpec = params.clawhubSpec?.trim();
|
||||
const npmSpec = params.npmSpec?.trim();
|
||||
if (clawhubSpec && params.defaultChoice !== "npm") {
|
||||
return clawhubSpec;
|
||||
}
|
||||
return npmSpec ?? clawhubSpec ?? params.pluginId;
|
||||
}
|
||||
|
||||
function pluginIdListIncludes(list: readonly string[] | undefined, pluginId: string): boolean {
|
||||
return Array.isArray(list) && list.some((entry) => entry.trim() === pluginId);
|
||||
}
|
||||
|
||||
function formatBlockedRuntimePluginGuidance(params: {
|
||||
cfg: OpenClawConfig;
|
||||
pluginId: string;
|
||||
}): string | undefined {
|
||||
const pluginId = params.pluginId;
|
||||
const alternative =
|
||||
pluginId === "acpx" ? "disable ACP/acpx in acp config" : 'change the runtime policy to "pi"';
|
||||
if (params.cfg.plugins?.enabled === false) {
|
||||
return `Enable plugin loading and the "${pluginId}" plugin, or ${alternative}.`;
|
||||
}
|
||||
if (pluginIdListIncludes(params.cfg.plugins?.deny, pluginId)) {
|
||||
return `Remove "${pluginId}" from plugins.deny and enable the "${pluginId}" plugin, or ${alternative}.`;
|
||||
}
|
||||
if (params.cfg.plugins?.entries?.[pluginId]?.enabled === false) {
|
||||
return `Set plugins.entries.${pluginId}.enabled=true or remove that disabled entry, or ${alternative}.`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function formatDisabledRuntimePluginGuidance(params: {
|
||||
cfg: OpenClawConfig;
|
||||
pluginId: string;
|
||||
}): string {
|
||||
const allow = params.cfg.plugins?.allow;
|
||||
const alternative =
|
||||
params.pluginId === "acpx"
|
||||
? "disable ACP/acpx in acp config"
|
||||
: 'change the runtime policy to "pi"';
|
||||
if (Array.isArray(allow) && allow.length > 0 && !allow.includes(params.pluginId)) {
|
||||
return `Add "${params.pluginId}" to plugins.allow and enable the plugin, or ${alternative}.`;
|
||||
}
|
||||
return `Enable the "${params.pluginId}" plugin, or ${alternative}.`;
|
||||
}
|
||||
|
||||
function collectConfiguredRuntimePluginWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
plugins: readonly { enabled?: boolean; id: string; status?: string }[];
|
||||
}): string[] {
|
||||
const enabledPluginIds = new Set(
|
||||
params.plugins
|
||||
.filter((plugin) => plugin.enabled !== false && plugin.status !== "disabled")
|
||||
.map((plugin) => plugin.id),
|
||||
);
|
||||
return collectConfiguredRuntimePluginIds(params.cfg, params.env, {
|
||||
includeEnvRuntime: false,
|
||||
includeImplicitRuntimePreferences: false,
|
||||
includeLegacyAgentRuntimes: false,
|
||||
}).flatMap((runtimeId) => {
|
||||
const candidate = resolveConfiguredRuntimePluginInstallCandidate(runtimeId);
|
||||
if (!candidate || enabledPluginIds.has(runtimeId)) {
|
||||
return [];
|
||||
}
|
||||
const disabledPluginRecord = params.plugins.find((plugin) => plugin.id === runtimeId);
|
||||
const blockedGuidance = formatBlockedRuntimePluginGuidance({
|
||||
cfg: params.cfg,
|
||||
pluginId: runtimeId,
|
||||
});
|
||||
if (blockedGuidance) {
|
||||
return [
|
||||
`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is blocked by plugin configuration. ${blockedGuidance}`,
|
||||
];
|
||||
}
|
||||
if (disabledPluginRecord) {
|
||||
return [
|
||||
`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is disabled. ${formatDisabledRuntimePluginGuidance({ cfg: params.cfg, pluginId: runtimeId })}`,
|
||||
];
|
||||
}
|
||||
const installSpec = formatConfiguredRuntimePluginInstallSpec(candidate);
|
||||
return [
|
||||
`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but no enabled "${runtimeId}" plugin was found. Run "openclaw doctor --fix" to install ${installSpec}, or install it manually with "openclaw plugins install ${installSpec}".`,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
export async function runPluginsEnableCommand(id: string): Promise<void> {
|
||||
assertConfigWriteAllowedInCurrentMode();
|
||||
|
||||
@@ -240,10 +337,16 @@ export async function runPluginsDoctorCommand(): Promise<void> {
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
autoRepairBlocked: isStalePluginAutoRepairBlocked(sourceCfg ?? cfg, process.env),
|
||||
});
|
||||
const configuredRuntimePluginWarnings = collectConfiguredRuntimePluginWarnings({
|
||||
cfg: sourceCfg ?? cfg,
|
||||
env: process.env,
|
||||
plugins: report.plugins,
|
||||
});
|
||||
const hasInstallTreeIssues =
|
||||
errors.length > 0 || diags.length > 0 || shadowed.length > 0 || compatibility.length > 0;
|
||||
const pluginConfigWarnings = [...stalePluginConfigWarnings, ...configuredRuntimePluginWarnings];
|
||||
|
||||
if (!hasInstallTreeIssues && stalePluginConfigWarnings.length === 0) {
|
||||
if (!hasInstallTreeIssues && pluginConfigWarnings.length === 0) {
|
||||
defaultRuntime.log("No plugin issues detected.");
|
||||
return;
|
||||
}
|
||||
@@ -301,14 +404,14 @@ export async function runPluginsDoctorCommand(): Promise<void> {
|
||||
lines.push(`- ${formatPluginCompatibilityNotice(notice)} [${marker}]`);
|
||||
}
|
||||
}
|
||||
if (stalePluginConfigWarnings.length > 0) {
|
||||
if (pluginConfigWarnings.length > 0) {
|
||||
if (lines.length > 0) {
|
||||
lines.push("");
|
||||
}
|
||||
lines.push(theme.warn("Plugin configuration:"));
|
||||
lines.push(...stalePluginConfigWarnings);
|
||||
lines.push(...pluginConfigWarnings);
|
||||
}
|
||||
if (!hasInstallTreeIssues && stalePluginConfigWarnings.length > 0) {
|
||||
if (!hasInstallTreeIssues && pluginConfigWarnings.length > 0) {
|
||||
if (lines.length > 0) {
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user