fix: keep plugin command status on cold index

This commit is contained in:
Shakker
2026-04-26 06:28:41 +01:00
parent 6e3eeb526f
commit a57d681db9
7 changed files with 32 additions and 22 deletions

View File

@@ -75,6 +75,8 @@ Docs: https://docs.openclaw.ai
- Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu.
- Plugins/CLI: preserve manifest name, description, format, and source metadata in cold `openclaw plugins list` output without importing plugin runtime. Thanks @shakkernerd.
- Security/audit: read channel exposure and plugin allowlist ownership from read-only plugin index metadata so cold audits do not depend on loaded channel runtime. Thanks @shakkernerd.
- Plugins/chat: keep `/plugins list`, `/plugins enable`, and `/plugins disable` on the persisted plugin index path so chat plugin management does not load diagnostic/runtime plugin registries before execution. Thanks @shakkernerd.
- Plugins/doctor: read workspace plugin status and legacy web-search ownership through installed-index manifest metadata instead of broad manifest registry scans. Thanks @shakkernerd.
- Logging: redact configured secret patterns at console and file-log sink exits
so credentials that reach the logger are masked before terminal display or
JSONL persistence. Fixes #67953. Thanks @Ziy1-Tan.

View File

@@ -6,7 +6,7 @@ import { buildPluginsCommandParams } from "./commands.test-harness.js";
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
const validateConfigObjectWithPluginsMock = vi.hoisted(() => vi.fn());
const writeConfigFileMock = vi.hoisted(() => vi.fn(async () => undefined));
const buildPluginSnapshotReportMock = vi.hoisted(() => vi.fn());
const buildPluginRegistrySnapshotReportMock = vi.hoisted(() => vi.fn());
const buildPluginDiagnosticsReportMock = vi.hoisted(() => vi.fn());
const buildPluginInspectReportMock = vi.hoisted(() => vi.fn());
const buildAllPluginInspectReportsMock = vi.hoisted(() => vi.fn());
@@ -69,7 +69,7 @@ vi.mock("../../plugins/status.js", () => ({
buildAllPluginInspectReports: buildAllPluginInspectReportsMock,
buildPluginDiagnosticsReport: buildPluginDiagnosticsReportMock,
buildPluginInspectReport: buildPluginInspectReportMock,
buildPluginSnapshotReport: buildPluginSnapshotReportMock,
buildPluginRegistrySnapshotReport: buildPluginRegistrySnapshotReportMock,
formatPluginCompatibilityNotice: formatPluginCompatibilityNoticeMock,
}));
@@ -121,7 +121,7 @@ describe("handlePluginsCommand", () => {
config: buildCfg(),
issues: [],
});
buildPluginSnapshotReportMock.mockReturnValue({
buildPluginRegistrySnapshotReportMock.mockReturnValue({
workspaceDir: "/tmp/plugins-workspace",
plugins: [
{
@@ -260,8 +260,8 @@ describe("handlePluginsCommand", () => {
);
});
it("resolves write targets by runtime-derived plugin name", async () => {
buildPluginDiagnosticsReportMock.mockReturnValue({
it("resolves write targets by indexed plugin name without loading diagnostics", async () => {
buildPluginRegistrySnapshotReportMock.mockReturnValue({
workspaceDir: "/tmp/plugins-workspace",
plugins: [
{
@@ -280,8 +280,8 @@ describe("handlePluginsCommand", () => {
const result = await handlePluginsCommand(params, true);
expect(result?.reply?.text).toContain('Plugin "superpowers" enabled');
expect(buildPluginDiagnosticsReportMock).toHaveBeenCalled();
expect(buildPluginSnapshotReportMock).not.toHaveBeenCalled();
expect(buildPluginRegistrySnapshotReportMock).toHaveBeenCalled();
expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled();
});
it("returns an explicit unauthorized reply for native /plugins list", async () => {

View File

@@ -26,7 +26,7 @@ import {
buildAllPluginInspectReports,
buildPluginDiagnosticsReport,
buildPluginInspectReport,
buildPluginSnapshotReport,
buildPluginRegistrySnapshotReport,
formatPluginCompatibilityNotice,
type PluginStatusReport,
} from "../../plugins/status.js";
@@ -308,7 +308,7 @@ async function loadPluginCommandState(
report:
options?.loadModules === true
? buildPluginDiagnosticsReport({ config, workspaceDir })
: buildPluginSnapshotReport({ config, workspaceDir }),
: buildPluginRegistrySnapshotReport({ config, workspaceDir }),
};
}
@@ -399,7 +399,7 @@ export const handlePluginsCommand: CommandHandler = async (params, allowTextComm
}
const loaded = await loadPluginCommandState(params.workspaceDir, {
loadModules: pluginsCommand.action !== "list",
loadModules: pluginsCommand.action === "inspect",
});
if (!loaded.ok) {
return {

View File

@@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({
resolveAgentWorkspaceDir: vi.fn(),
resolveDefaultAgentId: vi.fn(),
buildWorkspaceSkillStatus: vi.fn(),
buildPluginSnapshotReport: vi.fn(),
buildPluginRegistrySnapshotReport: vi.fn(),
buildPluginCompatibilityWarnings: vi.fn(),
listTaskFlowRecords: vi.fn<() => unknown[]>(() => []),
listTasksForFlowId: vi.fn<(flowId: string) => unknown[]>((_flowId: string) => []),
@@ -27,7 +27,8 @@ vi.mock("../agents/skills-status.js", () => ({
}));
vi.mock("../plugins/status.js", () => ({
buildPluginSnapshotReport: (...args: unknown[]) => mocks.buildPluginSnapshotReport(...args),
buildPluginRegistrySnapshotReport: (...args: unknown[]) =>
mocks.buildPluginRegistrySnapshotReport(...args),
buildPluginCompatibilityWarnings: (...args: unknown[]) =>
mocks.buildPluginCompatibilityWarnings(...args),
}));
@@ -53,7 +54,7 @@ async function runNoteWorkspaceStatusForTest(
mocks.buildWorkspaceSkillStatus.mockReturnValue({
skills: [],
});
mocks.buildPluginSnapshotReport.mockReturnValue({
mocks.buildPluginRegistrySnapshotReport.mockReturnValue({
workspaceDir: "/workspace",
...loadResult,
});
@@ -85,7 +86,7 @@ describe("noteWorkspaceStatus", () => {
}),
);
try {
expect(mocks.buildPluginSnapshotReport).toHaveBeenCalledWith({
expect(mocks.buildPluginRegistrySnapshotReport).toHaveBeenCalledWith({
config: {},
workspaceDir: "/workspace",
});
@@ -183,7 +184,7 @@ describe("noteWorkspaceStatus", () => {
"legacy-plugin still uses legacy before_agent_start",
]);
try {
expect(mocks.buildPluginSnapshotReport).toHaveBeenCalledWith({
expect(mocks.buildPluginRegistrySnapshotReport).toHaveBeenCalledWith({
config: {},
workspaceDir: "/workspace",
});

View File

@@ -2,7 +2,10 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { buildPluginCompatibilityWarnings, buildPluginSnapshotReport } from "../plugins/status.js";
import {
buildPluginCompatibilityWarnings,
buildPluginRegistrySnapshotReport,
} from "../plugins/status.js";
import { listTasksForFlowId } from "../tasks/runtime-internal.js";
import { listTaskFlowRecords } from "../tasks/task-flow-runtime-internal.js";
import { note } from "../terminal/note.js";
@@ -69,7 +72,7 @@ export function noteWorkspaceStatus(cfg: OpenClawConfig) {
"Skills status",
);
const pluginRegistry = buildPluginSnapshotReport({
const pluginRegistry = buildPluginRegistrySnapshotReport({
config: cfg,
workspaceDir,
});

View File

@@ -1,8 +1,8 @@
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
vi.mock("../../../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: () => ({
vi.mock("../../../plugins/plugin-registry.js", () => ({
loadPluginManifestRegistryForPluginRegistry: () => ({
plugins: [
{
id: "brave",

View File

@@ -1,7 +1,9 @@
import { mergeMissing } from "../../../config/legacy.shared.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { loadPluginManifestRegistry } from "../../../plugins/manifest-registry.js";
import { resolveManifestContractOwnerPluginId } from "../../../plugins/plugin-registry.js";
import {
loadPluginManifestRegistryForPluginRegistry,
resolveManifestContractOwnerPluginId,
} from "../../../plugins/plugin-registry.js";
import {
cloneRecord,
ensureRecord,
@@ -20,7 +22,9 @@ let legacyWebSearchProviderIdsCache: string[] | undefined;
let legacyWebSearchProviderIdSetCache: Set<string> | undefined;
function getLegacyWebSearchProviderIds(): string[] {
legacyWebSearchProviderIdsCache ??= loadPluginManifestRegistry({ cache: true })
legacyWebSearchProviderIdsCache ??= loadPluginManifestRegistryForPluginRegistry({
includeDisabled: true,
})
.plugins.filter((plugin) => plugin.origin === "bundled")
.flatMap((plugin) => plugin.contracts?.webSearchProviders ?? [])
.filter((providerId) => !NON_MIGRATED_LEGACY_WEB_SEARCH_PROVIDER_IDS.has(providerId))