mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-07 15:21:06 +00:00
feat(plugins): surface imported runtime state in status tooling (#59659)
* feat(plugins): surface imported runtime state * fix(plugins): keep status imports snapshot-only * fix(plugins): keep status snapshots manifest-only * fix(plugins): restore doctor load checks * refactor(plugins): split snapshot and diagnostics reports * fix(plugins): track imported erroring modules * fix(plugins): keep hot metadata where required * fix(plugins): keep hot doctor and write targeting * fix(plugins): track throwing module imports
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
import { resolveHookEntries } from "../hooks/policy.js";
|
||||
import type { HookEntry } from "../hooks/types.js";
|
||||
import { loadWorkspaceHookEntries } from "../hooks/workspace.js";
|
||||
import { buildPluginStatusReport } from "../plugins/status.js";
|
||||
import { buildPluginDiagnosticsReport } from "../plugins/status.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
|
||||
@@ -46,7 +46,7 @@ function mergeHookEntries(pluginEntries: HookEntry[], workspaceEntries: HookEntr
|
||||
function buildHooksReport(config: OpenClawConfig): HookStatusReport {
|
||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
const workspaceEntries = loadWorkspaceHookEntries(workspaceDir, { config });
|
||||
const pluginReport = buildPluginStatusReport({ config, workspaceDir });
|
||||
const pluginReport = buildPluginDiagnosticsReport({ config, workspaceDir });
|
||||
const pluginEntries = pluginReport.hooks.map((hook) => hook.entry);
|
||||
const entries = mergeHookEntries(pluginEntries, workspaceEntries);
|
||||
return buildWorkspaceHookStatus(workspaceDir, { config, entries });
|
||||
|
||||
@@ -18,7 +18,10 @@ export const resolveMarketplaceInstallShortcut = vi.fn();
|
||||
export const enablePluginInConfig = vi.fn();
|
||||
export const recordPluginInstall = vi.fn();
|
||||
export const clearPluginManifestRegistryCache = vi.fn();
|
||||
export const buildPluginStatusReport = vi.fn();
|
||||
export const buildPluginSnapshotReport = vi.fn();
|
||||
export const buildPluginDiagnosticsReport = vi.fn();
|
||||
export const buildPluginStatusReport = buildPluginDiagnosticsReport;
|
||||
export const buildPluginCompatibilityNotices = vi.fn();
|
||||
export const applyExclusiveSlotSelection = vi.fn();
|
||||
export const uninstallPlugin = vi.fn();
|
||||
export const updateNpmInstalledPlugins = vi.fn();
|
||||
@@ -72,7 +75,9 @@ vi.mock("../plugins/manifest-registry.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/status.js", () => ({
|
||||
buildPluginStatusReport: (...args: unknown[]) => buildPluginStatusReport(...args),
|
||||
buildPluginSnapshotReport: (...args: unknown[]) => buildPluginSnapshotReport(...args),
|
||||
buildPluginDiagnosticsReport: (...args: unknown[]) => buildPluginDiagnosticsReport(...args),
|
||||
buildPluginCompatibilityNotices: (...args: unknown[]) => buildPluginCompatibilityNotices(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/slots.js", () => ({
|
||||
@@ -154,7 +159,9 @@ export function resetPluginsCliTestState() {
|
||||
enablePluginInConfig.mockReset();
|
||||
recordPluginInstall.mockReset();
|
||||
clearPluginManifestRegistryCache.mockReset();
|
||||
buildPluginStatusReport.mockReset();
|
||||
buildPluginSnapshotReport.mockReset();
|
||||
buildPluginDiagnosticsReport.mockReset();
|
||||
buildPluginCompatibilityNotices.mockReset();
|
||||
applyExclusiveSlotSelection.mockReset();
|
||||
uninstallPlugin.mockReset();
|
||||
updateNpmInstalledPlugins.mockReset();
|
||||
@@ -199,10 +206,13 @@ export function resetPluginsCliTestState() {
|
||||
});
|
||||
enablePluginInConfig.mockImplementation((cfg: OpenClawConfig) => ({ config: cfg }));
|
||||
recordPluginInstall.mockImplementation((cfg: OpenClawConfig) => cfg);
|
||||
buildPluginStatusReport.mockReturnValue({
|
||||
const defaultPluginReport = {
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
};
|
||||
buildPluginSnapshotReport.mockReturnValue(defaultPluginReport);
|
||||
buildPluginDiagnosticsReport.mockReturnValue(defaultPluginReport);
|
||||
buildPluginCompatibilityNotices.mockReturnValue([]);
|
||||
applyExclusiveSlotSelection.mockImplementation(({ config }: { config: OpenClawConfig }) => ({
|
||||
config,
|
||||
warnings: [],
|
||||
|
||||
83
src/cli/plugins-cli.list.test.ts
Normal file
83
src/cli/plugins-cli.list.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { createPluginRecord } from "../plugins/status.test-helpers.js";
|
||||
import {
|
||||
buildPluginDiagnosticsReport,
|
||||
buildPluginSnapshotReport,
|
||||
resetPluginsCliTestState,
|
||||
runPluginsCommand,
|
||||
runtimeLogs,
|
||||
} from "./plugins-cli-test-helpers.js";
|
||||
|
||||
describe("plugins cli list", () => {
|
||||
beforeEach(() => {
|
||||
resetPluginsCliTestState();
|
||||
});
|
||||
|
||||
it("includes imported state in JSON output", async () => {
|
||||
buildPluginSnapshotReport.mockReturnValue({
|
||||
workspaceDir: "/workspace",
|
||||
plugins: [
|
||||
createPluginRecord({
|
||||
id: "demo",
|
||||
imported: true,
|
||||
activated: true,
|
||||
explicitlyEnabled: true,
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "list", "--json"]);
|
||||
|
||||
expect(buildPluginSnapshotReport).toHaveBeenCalledWith();
|
||||
|
||||
expect(JSON.parse(runtimeLogs[0] ?? "null")).toEqual({
|
||||
workspaceDir: "/workspace",
|
||||
plugins: [
|
||||
expect.objectContaining({
|
||||
id: "demo",
|
||||
imported: true,
|
||||
activated: true,
|
||||
explicitlyEnabled: true,
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("shows imported state in verbose output", async () => {
|
||||
buildPluginSnapshotReport.mockReturnValue({
|
||||
plugins: [
|
||||
createPluginRecord({
|
||||
id: "demo",
|
||||
name: "Demo Plugin",
|
||||
imported: false,
|
||||
activated: true,
|
||||
explicitlyEnabled: false,
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "list", "--verbose"]);
|
||||
|
||||
expect(buildPluginSnapshotReport).toHaveBeenCalledWith();
|
||||
|
||||
const output = runtimeLogs.join("\n");
|
||||
expect(output).toContain("activated: yes");
|
||||
expect(output).toContain("imported: no");
|
||||
expect(output).toContain("explicitly enabled: no");
|
||||
});
|
||||
|
||||
it("keeps doctor on a module-loading snapshot", async () => {
|
||||
buildPluginDiagnosticsReport.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "doctor"]);
|
||||
|
||||
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith();
|
||||
expect(runtimeLogs).toContain("No plugin issues detected.");
|
||||
});
|
||||
});
|
||||
@@ -12,9 +12,10 @@ import type { PluginRecord } from "../plugins/registry.js";
|
||||
import { formatPluginSourceForTable, resolvePluginSourceRoots } from "../plugins/source-display.js";
|
||||
import {
|
||||
buildAllPluginInspectReports,
|
||||
buildPluginDiagnosticsReport,
|
||||
buildPluginCompatibilityNotices,
|
||||
buildPluginInspectReport,
|
||||
buildPluginStatusReport,
|
||||
buildPluginSnapshotReport,
|
||||
formatPluginCompatibilityNotice,
|
||||
} from "../plugins/status.js";
|
||||
import {
|
||||
@@ -140,6 +141,9 @@ function formatPluginLine(plugin: PluginRecord, verbose = false): string {
|
||||
if (plugin.activated !== undefined) {
|
||||
parts.push(` activated: ${plugin.activated ? "yes" : "no"}`);
|
||||
}
|
||||
if (plugin.imported !== undefined) {
|
||||
parts.push(` imported: ${plugin.imported ? "yes" : "no"}`);
|
||||
}
|
||||
if (plugin.explicitlyEnabled !== undefined) {
|
||||
parts.push(` explicitly enabled: ${plugin.explicitlyEnabled ? "yes" : "no"}`);
|
||||
}
|
||||
@@ -236,7 +240,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--enabled", "Only show enabled plugins", false)
|
||||
.option("--verbose", "Show detailed entries", false)
|
||||
.action((opts: PluginsListOptions) => {
|
||||
const report = buildPluginStatusReport();
|
||||
const report = buildPluginSnapshotReport();
|
||||
const list = opts.enabled
|
||||
? report.plugins.filter((p) => p.status === "loaded")
|
||||
: report.plugins;
|
||||
@@ -338,7 +342,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--json", "Print JSON")
|
||||
.action((id: string | undefined, opts: PluginInspectOptions) => {
|
||||
const cfg = loadConfig();
|
||||
const report = buildPluginStatusReport({ config: cfg });
|
||||
const report = buildPluginDiagnosticsReport({ config: cfg });
|
||||
if (opts.all) {
|
||||
if (id) {
|
||||
defaultRuntime.error("Pass either a plugin id or --all, not both.");
|
||||
@@ -603,7 +607,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.action(async (id: string, opts: PluginUninstallOptions) => {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const report = buildPluginStatusReport({ config: cfg });
|
||||
const report = buildPluginDiagnosticsReport({ config: cfg });
|
||||
const extensionsDir = path.join(resolveStateDir(process.env, os.homedir), "extensions");
|
||||
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
||||
|
||||
@@ -790,7 +794,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.command("doctor")
|
||||
.description("Report plugin load issues")
|
||||
.action(() => {
|
||||
const report = buildPluginStatusReport();
|
||||
const report = buildPluginDiagnosticsReport();
|
||||
const errors = report.plugins.filter((p) => p.status === "error");
|
||||
const diags = report.diagnostics.filter((d) => d.level === "error");
|
||||
const compatibility = buildPluginCompatibilityNotices({ report });
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub.js";
|
||||
import { applyExclusiveSlotSelection } from "../plugins/slots.js";
|
||||
import { buildPluginStatusReport } from "../plugins/status.js";
|
||||
import { buildPluginDiagnosticsReport } from "../plugins/status.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
|
||||
@@ -40,7 +40,7 @@ export function applySlotSelectionForPlugin(
|
||||
config: OpenClawConfig,
|
||||
pluginId: string,
|
||||
): { config: OpenClawConfig; warnings: string[] } {
|
||||
const report = buildPluginStatusReport({ config });
|
||||
const report = buildPluginDiagnosticsReport({ config });
|
||||
const plugin = report.plugins.find((entry) => entry.id === pluginId);
|
||||
if (!plugin) {
|
||||
return { config, warnings: [] };
|
||||
|
||||
Reference in New Issue
Block a user