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:
Zavian Wang
2026-05-17 14:13:55 +08:00
committed by GitHub
parent 826c2f4517
commit 9a11e76458
7 changed files with 483 additions and 36 deletions

View File

@@ -138,6 +138,263 @@ describe("plugins cli list", () => {
expect(output).not.toContain("No plugin issues detected.");
});
it("reports missing configured Codex runtime plugin in doctor output", async () => {
const sourceConfig = {
agents: {
defaults: {
models: {
"openai/gpt-5.5": {
agentRuntime: { id: "codex" },
},
},
},
},
};
loadConfig.mockReturnValue(sourceConfig);
readConfigFileSnapshot.mockResolvedValueOnce({
path: "/tmp/openclaw-config.json5",
exists: true,
raw: "{}",
parsed: sourceConfig,
resolved: sourceConfig,
sourceConfig,
runtimeConfig: sourceConfig,
config: sourceConfig,
valid: true,
hash: "mock",
issues: [],
warnings: [],
legacyIssues: [],
});
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain("Plugin configuration:");
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
expect(output).toContain("openclaw doctor --fix");
expect(output).toContain("openclaw plugins install @openclaw/codex");
expect(output).toContain(
"No plugin install-tree issues detected; configuration warnings remain.",
);
expect(output).not.toContain("No plugin issues detected.");
});
it("reports missing configured ACPX runtime plugin in doctor output", async () => {
const sourceConfig = {
acp: {
backend: "acpx",
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain("Plugin configuration:");
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
expect(output).toContain("openclaw doctor --fix");
expect(output).toContain("openclaw plugins install @openclaw/acpx");
expect(output).not.toContain("No plugin issues detected.");
});
it("reports blocked configured ACPX runtime with ACP-specific guidance", async () => {
const sourceConfig = {
acp: {
backend: "acpx",
},
plugins: {
entries: {
acpx: { enabled: false },
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
expect(output).toContain("Set plugins.entries.acpx.enabled=true");
expect(output).toContain("disable ACP/acpx in acp config");
expect(output).not.toContain('runtime policy to "pi"');
expect(output).not.toContain("openclaw plugins install @openclaw/acpx");
expect(output).not.toContain("No plugin issues detected.");
});
it("reports disabled configured ACPX runtime with ACP-specific guidance", async () => {
const sourceConfig = {
acp: {
backend: "acpx",
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [createPluginRecord({ id: "acpx", enabled: false, status: "disabled" })],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
expect(output).toContain('Enable the "acpx" plugin');
expect(output).toContain("disable ACP/acpx in acp config");
expect(output).not.toContain('runtime policy to "pi"');
expect(output).not.toContain("openclaw plugins install @openclaw/acpx");
expect(output).not.toContain("No plugin issues detected.");
});
it("does not report implicit OpenAI Codex preference as configured runtime", async () => {
const sourceConfig = {
agents: {
defaults: {
model: "openai/gpt-5.5",
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).not.toContain('Configured runtime "codex"');
expect(output).toContain("No plugin issues detected.");
});
it("does not report configured Codex runtime when the plugin is enabled", async () => {
const sourceConfig = {
agents: {
defaults: {
models: {
"openai/gpt-5.5": {
agentRuntime: { id: "codex" },
},
},
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [createPluginRecord({ id: "codex" })],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
expect(runtimeLogs).toContain("No plugin issues detected.");
});
it("reports configured Codex runtime when the plugin record is disabled", async () => {
const sourceConfig = {
agents: {
defaults: {
models: {
"openai/gpt-5.5": {
agentRuntime: { id: "codex" },
},
},
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [createPluginRecord({ id: "codex", enabled: false, status: "disabled" })],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
expect(output).toContain('but "codex" is disabled');
expect(output).toContain('Enable the "codex" plugin');
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
expect(output).not.toContain("No plugin issues detected.");
});
it("reports blocked configured Codex runtime without install advice", async () => {
const sourceConfig = {
plugins: {
deny: ["codex"],
},
agents: {
defaults: {
models: {
"openai/gpt-5.5": {
agentRuntime: { id: "codex" },
},
},
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
expect(output).toContain('but "codex" is blocked by plugin configuration');
expect(output).toContain('Remove "codex" from plugins.deny');
expect(output).not.toContain('Run "openclaw doctor --fix" to install');
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
expect(output).not.toContain("No plugin issues detected.");
});
it("reports disabled configured Codex runtime entry without install advice", async () => {
const sourceConfig = {
plugins: {
entries: {
codex: { enabled: false },
},
},
agents: {
defaults: {
models: {
"openai/gpt-5.5": {
agentRuntime: { id: "codex" },
},
},
},
},
};
loadConfig.mockReturnValue(sourceConfig);
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [],
diagnostics: [],
});
await runPluginsCommand(["plugins", "doctor"]);
const output = runtimeLogs.join("\n");
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
expect(output).toContain('but "codex" is blocked by plugin configuration');
expect(output).toContain("Set plugins.entries.codex.enabled=true");
expect(output).not.toContain('Run "openclaw doctor --fix" to install');
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
expect(output).not.toContain("No plugin issues detected.");
});
it("reports config-selected plugin source shadowing in doctor output", async () => {
buildPluginDiagnosticsReport.mockReturnValue({
plugins: [