mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 21:56:09 +00:00
improve(status): group disabled plugins by reason in /status plugins (#99598)
This commit is contained in:
@@ -243,6 +243,7 @@ describe("plugin health status formatting", () => {
|
||||
);
|
||||
expect(text).toContain("Loaded: 1 (ok-plugin)");
|
||||
expect(text).toContain("Disabled: 1");
|
||||
expect(text).toContain("- disabled: 1 (disabled-plugin)");
|
||||
expect(text).toContain("- bad-plugin [load]: module failed");
|
||||
expect(text).toContain("- bad_tool owner=plugin:bad-tools: unsupported anyOf");
|
||||
expect(text).toContain("- sms plugin=sms-plugin [setup]: setup failed");
|
||||
@@ -251,6 +252,68 @@ describe("plugin health status formatting", () => {
|
||||
expect(text).toContain("Full inventory: /plugins list");
|
||||
});
|
||||
|
||||
it("groups disabled plugins by their recorded disable reason", () => {
|
||||
const text = formatDetailedPluginHealth({
|
||||
plugins: [
|
||||
{ id: "zeta", status: "disabled", enabled: false, error: "not in allowlist" },
|
||||
{
|
||||
id: "alpha",
|
||||
status: "disabled",
|
||||
enabled: false,
|
||||
error: "overridden by better-alpha plugin",
|
||||
},
|
||||
{ id: "beta", status: "disabled", enabled: false, error: "not in allowlist" },
|
||||
// No recorded reason (hand-built snapshot): falls back to a plain "disabled".
|
||||
{ id: "mid", status: "disabled", enabled: false },
|
||||
],
|
||||
diagnostics: [],
|
||||
contextEngineQuarantines: [],
|
||||
});
|
||||
|
||||
const lines = text.split("\n");
|
||||
const disabledAt = lines.indexOf("Disabled: 4");
|
||||
expect(disabledAt).toBeGreaterThan(-1);
|
||||
// One line per distinct reason, right under the count, in deterministic
|
||||
// reason order with alphabetical plugin ids.
|
||||
expect(lines.slice(disabledAt + 1, disabledAt + 4)).toEqual([
|
||||
"- disabled: 1 (mid)",
|
||||
"- not in allowlist: 2 (beta, zeta)",
|
||||
"- overridden by better-alpha plugin: 1 (alpha)",
|
||||
]);
|
||||
});
|
||||
|
||||
it("caps disabled reason lines and per-reason id lists", () => {
|
||||
const distinctReasons = formatDetailedPluginHealth({
|
||||
plugins: Array.from({ length: 9 }, (_, index) => ({
|
||||
id: `plugin-${index}`,
|
||||
status: "disabled" as const,
|
||||
enabled: false,
|
||||
error: `reason ${index}`,
|
||||
})),
|
||||
diagnostics: [],
|
||||
contextEngineQuarantines: [],
|
||||
});
|
||||
expect(distinctReasons).toContain("Disabled: 9");
|
||||
expect(distinctReasons).toContain("- reason 7: 1 (plugin-7)");
|
||||
expect(distinctReasons).not.toContain("reason 8");
|
||||
expect(distinctReasons).toContain("- +1 more reasons");
|
||||
|
||||
const sharedReason = formatDetailedPluginHealth({
|
||||
plugins: Array.from({ length: 10 }, (_, index) => ({
|
||||
id: `plugin-${index}`,
|
||||
status: "disabled" as const,
|
||||
enabled: false,
|
||||
error: "not in allowlist",
|
||||
})),
|
||||
diagnostics: [],
|
||||
contextEngineQuarantines: [],
|
||||
});
|
||||
expect(sharedReason).toContain("Disabled: 10");
|
||||
expect(sharedReason).toContain(
|
||||
"- not in allowlist: 10 (plugin-0, plugin-1, plugin-2, plugin-3, plugin-4, plugin-5, plugin-6, plugin-7, +2 more)",
|
||||
);
|
||||
});
|
||||
|
||||
it("separates runtime-loaded plugins from installed-but-not-active inventory", () => {
|
||||
const text = formatDetailedPluginHealth({
|
||||
plugins: [
|
||||
|
||||
@@ -297,7 +297,9 @@ export function formatDetailedPluginHealth(snapshot: StatusPluginHealthSnapshot)
|
||||
.filter((id) => !shouldRunNotLoadedSet.has(id))
|
||||
.toSorted(byLocale)
|
||||
: [];
|
||||
const disabled = snapshot.plugins.filter((plugin) => plugin.status === "disabled").length;
|
||||
const disabledPlugins = snapshot.plugins
|
||||
.filter((plugin) => plugin.status === "disabled")
|
||||
.toSorted((left, right) => byLocale(left.id, right.id));
|
||||
const errors = snapshot.plugins
|
||||
.filter((plugin) => plugin.status === "error")
|
||||
.toSorted((left, right) => byLocale(left.id, right.id));
|
||||
@@ -327,9 +329,39 @@ export function formatDetailedPluginHealth(snapshot: StatusPluginHealthSnapshot)
|
||||
const lines = [
|
||||
formatCompactPluginHealthLine(snapshot) ?? "🔌 Plugins: OK",
|
||||
`Loaded: ${loaded.length}${loaded.length > 0 ? ` (${formatPluginList(loaded, 8)})` : ""}`,
|
||||
`Disabled: ${disabled}`,
|
||||
`Disabled: ${disabledPlugins.length}`,
|
||||
];
|
||||
|
||||
if (disabledPlugins.length > 0) {
|
||||
// Disable decisions record their reason on `error` (config off, allow/denylist,
|
||||
// overridden-by/memory-slot arbitration). Group ids per distinct reason so the
|
||||
// detailed view answers "why is this plugin off" without a /plugins round-trip,
|
||||
// and a restrictive allowlist folds into one bounded line instead of dozens.
|
||||
const disabledByReason = new Map<string, string[]>();
|
||||
for (const plugin of disabledPlugins) {
|
||||
const reason = plugin.error ?? "disabled";
|
||||
const ids = disabledByReason.get(reason);
|
||||
if (ids) {
|
||||
ids.push(plugin.id);
|
||||
} else {
|
||||
disabledByReason.set(reason, [plugin.id]);
|
||||
}
|
||||
}
|
||||
const reasonEntries = [...disabledByReason.entries()].toSorted((left, right) =>
|
||||
byLocale(left[0], right[0]),
|
||||
);
|
||||
lines.push(
|
||||
...reasonEntries
|
||||
.slice(0, 8)
|
||||
.map(([reason, ids]) => `- ${reason}: ${ids.length} (${formatPluginList(ids, 8)})`),
|
||||
);
|
||||
if (reasonEntries.length > 8) {
|
||||
// Unlike the per-plugin buckets, the count above tallies plugins, not
|
||||
// reasons, so a reader cannot infer that reason lines were truncated.
|
||||
lines.push(`- +${reasonEntries.length - 8} more reasons`);
|
||||
}
|
||||
}
|
||||
|
||||
if (installedNotActive.length > 0) {
|
||||
// Installed/discovered plugins not loaded in the runtime registry. Neutral
|
||||
// inventory, not an error: the gateway only starts the plugins its startup
|
||||
|
||||
Reference in New Issue
Block a user