diff --git a/CHANGELOG.md b/CHANGELOG.md index 52fc4346d6c..3c3f654a0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai - Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock and keep Docker bundled plugin runtime deps/mirrors in a Docker-managed volume instead of the Windows/WSL config bind mount, so cold starts avoid slow host-volume mirror writes. Fixes #73339. Thanks @1yihui. - Channels/LINE: persist inbound image, video, audio, and file downloads in `~/.openclaw/media/inbound/` instead of temporary files so agents can still read LINE media after `/tmp` cleanup. Fixes #73370. Thanks @hijirii and @wenxu007. - CLI/plugins: keep bundled plugin installs out of `plugins.load.paths` while preserving install records, so install/inspect/doctor loops no longer warn about the current bundled plugin directory. Thanks @vincentkoc. +- CLI/plugins: scope `plugins inspect ` runtime loading to the matched plugin so single-plugin inspection does not load every plugin before checking the target. Thanks @shakkernerd. - CLI/plugins: remove managed copied-path plugin directories during uninstall and plan uninstall from metadata instead of runtime-loading plugins, so plugin lifecycle commands avoid unnecessary bundled runtime-deps work. Thanks @shakkernerd. - Cron tool: infer the creating session's agentId for `cron.add` jobs when `agentId` is omitted or passed as undefined, keeping scheduled agentTurn jobs routed to the session agent; #40571 identified the guard bug and supplied the focused regression coverage. Thanks @ChanningYul. - Cron/Telegram: add `--thread-id` to `openclaw cron add` and `openclaw cron edit`, preserving Telegram forum topic delivery targets across scheduled announcements. Carries forward #51581, #60373, and #60890. Thanks @ChunHao-dev. diff --git a/src/cli/plugins-cli.list.test.ts b/src/cli/plugins-cli.list.test.ts index 07ffd5d5a13..2807fef86bc 100644 --- a/src/cli/plugins-cli.list.test.ts +++ b/src/cli/plugins-cli.list.test.ts @@ -4,10 +4,12 @@ import { buildPluginDiagnosticsReport, buildPluginInspectReport, buildPluginRegistrySnapshotReport, + buildPluginSnapshotReport, inspectPluginRegistry, resetPluginsCliTestState, refreshPluginRegistry, runPluginsCommand, + runtimeErrors, runtimeLogs, } from "./plugins-cli-test-helpers.js"; @@ -119,6 +121,10 @@ describe("plugins cli list", () => { }); it("shows conversation-access hook policy in inspect output", async () => { + buildPluginSnapshotReport.mockReturnValue({ + plugins: [createPluginRecord({ id: "openclaw-mem0", name: "Mem0" })], + diagnostics: [], + }); buildPluginInspectReport.mockReturnValue({ workspaceDir: "/workspace", plugin: createPluginRecord({ id: "openclaw-mem0", name: "Mem0" }), @@ -150,7 +156,26 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "inspect", "openclaw-mem0"]); + expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ + config: {}, + onlyPluginIds: ["openclaw-mem0"], + }); expect(runtimeLogs.join("\n")).toContain("Policy"); expect(runtimeLogs.join("\n")).toContain("allowConversationAccess: true"); }); + + it("does not runtime-load plugins when inspect target is missing", async () => { + buildPluginSnapshotReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await expect(runPluginsCommand(["plugins", "inspect", "missing-plugin"])).rejects.toThrow( + "__exit__:1", + ); + + expect(buildPluginSnapshotReport).toHaveBeenCalledWith({ config: {} }); + expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled(); + expect(runtimeErrors.at(-1)).toContain("Plugin not found: missing-plugin"); + }); }); diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index eed597b0569..85fe8cc43d1 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -259,24 +259,26 @@ export function registerPluginsCli(program: Command) { buildAllPluginInspectReports, buildPluginDiagnosticsReport, buildPluginInspectReport, + buildPluginSnapshotReport, formatPluginCompatibilityNotice, } = await import("../plugins/status.js"); const { loadInstalledPluginIndexInstallRecords } = await import("../plugins/installed-plugin-index-records.js"); const cfg = getRuntimeConfig(); const installRecords = await loadInstalledPluginIndexInstallRecords(); - const report = buildPluginDiagnosticsReport({ - config: cfg, - ...(opts.json ? { logger: quietPluginJsonLogger } : {}), - }); + const loggerParams = opts.json ? { logger: quietPluginJsonLogger } : {}; if (opts.all) { if (id) { defaultRuntime.error("Pass either a plugin id or --all, not both."); return defaultRuntime.exit(1); } + const report = buildPluginDiagnosticsReport({ + config: cfg, + ...loggerParams, + }); const inspectAll = buildAllPluginInspectReports({ config: cfg, - ...(opts.json ? { logger: quietPluginJsonLogger } : {}), + ...loggerParams, report, }); const inspectAllWithInstall = inspectAll.map((inspect) => ({ @@ -342,10 +344,26 @@ export function registerPluginsCli(program: Command) { return defaultRuntime.exit(1); } - const inspect = buildPluginInspectReport({ - id, + const snapshotReport = buildPluginSnapshotReport({ config: cfg, - ...(opts.json ? { logger: quietPluginJsonLogger } : {}), + ...loggerParams, + }); + const targetPlugin = snapshotReport.plugins.find( + (entry) => entry.id === id || entry.name === id, + ); + if (!targetPlugin) { + defaultRuntime.error(`Plugin not found: ${id}`); + return defaultRuntime.exit(1); + } + const report = buildPluginDiagnosticsReport({ + config: cfg, + ...loggerParams, + onlyPluginIds: [targetPlugin.id], + }); + const inspect = buildPluginInspectReport({ + id: targetPlugin.id, + config: cfg, + ...loggerParams, report, }); if (!inspect) { diff --git a/src/plugins/status.ts b/src/plugins/status.ts index 3a9a44bee32..4508111fa9b 100644 --- a/src/plugins/status.ts +++ b/src/plugins/status.ts @@ -161,6 +161,7 @@ function resolveReportedPluginVersion( type PluginReportParams = { config?: OpenClawConfig; effectiveOnly?: boolean; + onlyPluginIds?: readonly string[]; workspaceDir?: string; /** Use an explicit env when plugin roots should resolve independently from process.env. */ env?: NodeJS.ProcessEnv; @@ -295,7 +296,9 @@ function buildPluginReport( workspaceDir, env: params?.env ?? process.env, }) - : undefined; + : params?.onlyPluginIds === undefined + ? undefined + : [...params.onlyPluginIds]; const registry = loadModules ? loadOpenClawPlugins(