mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
fix(plugins): reuse cold inspect registry snapshots (#75620)
Summary: - The PR reuses a request-scoped cold manifest registry/runtime context across plugin status and inspect report paths, threads that context through provider/setup/metadata helpers, adds targeted coverage, and adds a changelog entry. ClawSweeper fixups: - Included follow-up commit: fix(plugins): preserve setup auto-enable lookup Validation: - ClawSweeper review passed for head4d8e8e2d24. - Required merge gates passed before the squash merge. Prepared head SHA:4d8e8e2d24Review: https://github.com/openclaw/openclaw/pull/75620#issuecomment-4359143053 Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/Slack: defer status-reaction cleanup until run finalization so queued, thinking, tool, and terminal reactions no longer flicker during normal progress updates. (#75582)
|
||||
- Discord/voice: leave Discord voice off for text-only configs unless `channels.discord.voice` is explicitly configured, avoiding default `GuildVoiceStates` traffic and idle gateway CPU pressure for bots that do not use `/vc`. Fixes #73753; refs #74044. Thanks @sanchezm86 and @SecureCloudProjO.
|
||||
- Discord/voice: rerun configured voice auto-join after Discord gateway RESUMED events and ignore already-destroyed stale voice connections during reconnect cleanup, so health-monitor account restarts can rejoin configured channels. Fixes #40665. Thanks @liz709.
|
||||
- Plugins/CLI: reuse the cold manifest registry while building plugin status and inspect reports, so large configured plugin sets no longer rediscover the bundled/plugin registry once per inspect row. Thanks @vincentkoc.
|
||||
- Discord/voice: lengthen the default voice join Ready wait, add configurable `voice.connectTimeoutMs`/`voice.reconnectGraceMs`, and warn before destroying unrecovered disconnected sessions so slow Discord voice handshakes and reconnects no longer fail silently. Fixes #63098; refs #39825 and #65039. Thanks @darealgege, @kzicherman, and @ayochim.
|
||||
- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) Thanks @clawsweeper.
|
||||
- Gateway/sessions: keep session-store reads from running stale prune and entry-count cap maintenance during startup, so oversized stores no longer block chat history readiness after updates while writes and `sessions cleanup --enforce` still preserve the cleanup safeguards. Fixes #70050. Thanks @tangda18.
|
||||
|
||||
@@ -131,7 +131,20 @@ export function resolveBundledProviderCompatPluginIds(params: {
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
onlyPluginIds?: readonly string[];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): string[] {
|
||||
if (params.manifestRegistry) {
|
||||
const onlyPluginIdSet = createPluginIdScopeSet(params.onlyPluginIds);
|
||||
return params.manifestRegistry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
plugin.origin === "bundled" &&
|
||||
plugin.providers.length > 0 &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)),
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
const providerSurfacePluginIds = resolveProviderSurfacePluginIdSet({ ...params, registry });
|
||||
return listRegistryPluginIds(
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { createSubsystemLogger } from "../../logging.js";
|
||||
import { resolvePluginActivationSourceConfig } from "../activation-source-config.js";
|
||||
import type { PluginLoadOptions } from "../loader.js";
|
||||
import type { PluginManifestRegistry } from "../manifest-registry.js";
|
||||
import type { PluginLogger } from "../types.js";
|
||||
|
||||
const log = createSubsystemLogger("plugins");
|
||||
@@ -30,6 +31,7 @@ export type PluginRuntimeLoadContextOptions = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
logger?: PluginLogger;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
};
|
||||
|
||||
export function createPluginRuntimeLoaderLogger(): PluginLogger {
|
||||
@@ -50,7 +52,11 @@ export function resolvePluginRuntimeLoadContext(
|
||||
config: rawConfig,
|
||||
activationSourceConfig: options?.activationSourceConfig,
|
||||
});
|
||||
const autoEnabled = applyPluginAutoEnable({ config: rawConfig, env });
|
||||
const autoEnabled = applyPluginAutoEnable({
|
||||
config: rawConfig,
|
||||
env,
|
||||
manifestRegistry: options?.manifestRegistry,
|
||||
});
|
||||
const config = autoEnabled.config;
|
||||
const workspaceDir =
|
||||
options?.workspaceDir ?? resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
|
||||
@@ -103,6 +103,47 @@ describe("loadPluginMetadataRegistrySnapshot", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("honors explicit load options when reusing a resolved runtime context", () => {
|
||||
const logger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
const env = { HOME: "/tmp/context-home" } as NodeJS.ProcessEnv;
|
||||
const manifestRegistry = { plugins: [], diagnostics: [] };
|
||||
|
||||
loadPluginMetadataRegistrySnapshot({
|
||||
config: { plugins: { allow: ["compat-provider"] } },
|
||||
activationSourceConfig: { plugins: { allow: ["raw-plugin"] } },
|
||||
workspaceDir: "/compat-workspace",
|
||||
env,
|
||||
logger,
|
||||
manifestRegistry,
|
||||
runtimeContext: {
|
||||
rawConfig: { plugins: { allow: ["raw-plugin"] } },
|
||||
config: { plugins: { allow: ["raw-plugin"] } },
|
||||
activationSourceConfig: { plugins: { allow: ["raw-plugin"] } },
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/context-workspace",
|
||||
env,
|
||||
logger,
|
||||
},
|
||||
});
|
||||
|
||||
expect(applyPluginAutoEnableMock).not.toHaveBeenCalled();
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: { plugins: { allow: ["compat-provider"] } },
|
||||
activationSourceConfig: { plugins: { allow: ["raw-plugin"] } },
|
||||
workspaceDir: "/compat-workspace",
|
||||
env,
|
||||
logger,
|
||||
manifestRegistry,
|
||||
mode: "validate",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves explicit empty plugin scopes on metadata snapshots", () => {
|
||||
loadPluginMetadataRegistrySnapshot({
|
||||
config: { plugins: {} },
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { loadOpenClawPlugins } from "../loader.js";
|
||||
import type { PluginManifestRegistry } from "../manifest-registry.js";
|
||||
import { hasExplicitPluginIdScope } from "../plugin-scope.js";
|
||||
import type { PluginRegistry } from "../registry.js";
|
||||
import type { PluginLogger } from "../types.js";
|
||||
import { buildPluginRuntimeLoadOptions, resolvePluginRuntimeLoadContext } from "./load-context.js";
|
||||
import {
|
||||
buildPluginRuntimeLoadOptions,
|
||||
resolvePluginRuntimeLoadContext,
|
||||
type PluginRuntimeLoadContext,
|
||||
} from "./load-context.js";
|
||||
|
||||
export function loadPluginMetadataRegistrySnapshot(options?: {
|
||||
config?: OpenClawConfig;
|
||||
@@ -13,11 +18,20 @@ export function loadPluginMetadataRegistrySnapshot(options?: {
|
||||
workspaceDir?: string;
|
||||
onlyPluginIds?: string[];
|
||||
loadModules?: boolean;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
runtimeContext?: PluginRuntimeLoadContext;
|
||||
}): PluginRegistry {
|
||||
const context = resolvePluginRuntimeLoadContext(options);
|
||||
const context = options?.runtimeContext ?? resolvePluginRuntimeLoadContext(options);
|
||||
|
||||
return loadOpenClawPlugins(
|
||||
buildPluginRuntimeLoadOptions(context, {
|
||||
...(options?.config !== undefined ? { config: options.config } : {}),
|
||||
...(options?.activationSourceConfig !== undefined
|
||||
? { activationSourceConfig: options.activationSourceConfig }
|
||||
: {}),
|
||||
...(options?.workspaceDir !== undefined ? { workspaceDir: options.workspaceDir } : {}),
|
||||
...(options?.env !== undefined ? { env: options.env } : {}),
|
||||
...(options?.logger !== undefined ? { logger: options.logger } : {}),
|
||||
throwOnLoadError: true,
|
||||
cache: false,
|
||||
activate: false,
|
||||
@@ -26,6 +40,7 @@ export function loadPluginMetadataRegistrySnapshot(options?: {
|
||||
...(hasExplicitPluginIdScope(options?.onlyPluginIds)
|
||||
? { onlyPluginIds: options?.onlyPluginIds }
|
||||
: {}),
|
||||
...(options?.manifestRegistry ? { manifestRegistry: options.manifestRegistry } : {}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { buildPluginApi } from "./api-builder.js";
|
||||
import { collectPluginConfigContractMatches } from "./config-contracts.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
import { listSetupCliBackendIds, listSetupProviderIds } from "./setup-descriptors.js";
|
||||
@@ -417,6 +417,7 @@ export function resolvePluginSetupRegistry(params?: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
pluginIds?: readonly string[];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): PluginSetupRegistry {
|
||||
const env = params?.env ?? process.env;
|
||||
const scopedPluginIds = params?.pluginIds
|
||||
@@ -441,12 +442,14 @@ export function resolvePluginSetupRegistry(params?: {
|
||||
const providerKeys = new Set<string>();
|
||||
const cliBackendKeys = new Set<string>();
|
||||
|
||||
const manifestRegistry = loadSetupManifestRegistry({
|
||||
config: params?.config,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
pluginIds: params?.pluginIds,
|
||||
});
|
||||
const manifestRegistry =
|
||||
params?.manifestRegistry ??
|
||||
loadSetupManifestRegistry({
|
||||
config: params?.config,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
pluginIds: params?.pluginIds,
|
||||
});
|
||||
|
||||
for (const record of manifestRegistry.plugins) {
|
||||
if (scopedPluginIds && !scopedPluginIds.has(record.id)) {
|
||||
@@ -703,6 +706,7 @@ export function resolvePluginSetupAutoEnableReasons(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
pluginIds?: readonly string[];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): SetupAutoEnableReason[] {
|
||||
const env = params.env ?? process.env;
|
||||
const reasons: SetupAutoEnableReason[] = [];
|
||||
@@ -713,6 +717,7 @@ export function resolvePluginSetupAutoEnableReasons(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
pluginIds: params.pluginIds,
|
||||
manifestRegistry: params.manifestRegistry,
|
||||
}).autoEnableProbes) {
|
||||
const raw = entry.probe({
|
||||
config: params.config,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
const loadConfigMock = vi.fn();
|
||||
const loadOpenClawPluginsMock = vi.fn();
|
||||
const loadPluginMetadataRegistrySnapshotMock = vi.fn();
|
||||
const loadPluginManifestRegistryForPluginRegistryMock = vi.fn();
|
||||
const loadPluginRegistrySnapshotWithMetadataMock = vi.fn();
|
||||
const loadPluginManifestRegistryForInstalledIndexMock = vi.fn();
|
||||
const applyPluginAutoEnableMock = vi.fn();
|
||||
@@ -50,6 +51,8 @@ vi.mock("./runtime/metadata-registry-loader.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./plugin-registry.js", () => ({
|
||||
loadPluginManifestRegistryForPluginRegistry: (...args: unknown[]) =>
|
||||
loadPluginManifestRegistryForPluginRegistryMock(...args),
|
||||
loadPluginRegistrySnapshotWithMetadata: (...args: unknown[]) =>
|
||||
loadPluginRegistrySnapshotWithMetadataMock(...args),
|
||||
}));
|
||||
@@ -186,10 +189,12 @@ function expectMetadataSnapshotLoaderCall(params: {
|
||||
}
|
||||
|
||||
function expectAutoEnabledStatusLoad(params: { rawConfig: unknown }) {
|
||||
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith({
|
||||
config: params.rawConfig,
|
||||
env: process.env,
|
||||
});
|
||||
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: params.rawConfig,
|
||||
env: process.env,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function createCompatChainFixture() {
|
||||
@@ -363,6 +368,7 @@ describe("plugin status reports", () => {
|
||||
loadConfigMock.mockReset();
|
||||
loadOpenClawPluginsMock.mockReset();
|
||||
loadPluginMetadataRegistrySnapshotMock.mockReset();
|
||||
loadPluginManifestRegistryForPluginRegistryMock.mockReset();
|
||||
loadPluginRegistrySnapshotWithMetadataMock.mockReset();
|
||||
loadPluginManifestRegistryForInstalledIndexMock.mockReset();
|
||||
applyPluginAutoEnableMock.mockReset();
|
||||
@@ -377,6 +383,10 @@ describe("plugin status reports", () => {
|
||||
source: "derived",
|
||||
diagnostics: [],
|
||||
});
|
||||
loadPluginManifestRegistryForPluginRegistryMock.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
|
||||
import { getRuntimeConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
@@ -24,6 +25,7 @@ import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { PluginDiagnostic } from "./manifest-types.js";
|
||||
import { tracePluginLifecyclePhase } from "./plugin-lifecycle-trace.js";
|
||||
import {
|
||||
loadPluginManifestRegistryForPluginRegistry,
|
||||
loadPluginRegistrySnapshotWithMetadata,
|
||||
type PluginRegistrySnapshotDiagnostic,
|
||||
type PluginRegistrySnapshotSource,
|
||||
@@ -167,6 +169,7 @@ type PluginReportParams = {
|
||||
/** Use an explicit env when plugin roots should resolve independently from process.env. */
|
||||
env?: NodeJS.ProcessEnv;
|
||||
logger?: PluginLogger;
|
||||
resolvedConfig?: OpenClawConfig;
|
||||
};
|
||||
|
||||
function buildPluginRecordFromInstalledIndex(
|
||||
@@ -259,13 +262,27 @@ function buildPluginReport(
|
||||
params: PluginReportParams | undefined,
|
||||
loadModules: boolean,
|
||||
): PluginStatusReport {
|
||||
const rawConfig = params?.config ?? getRuntimeConfig();
|
||||
const initialWorkspaceDir =
|
||||
params?.workspaceDir ??
|
||||
resolveAgentWorkspaceDir(rawConfig, resolveDefaultAgentId(rawConfig), params?.env);
|
||||
const manifestRegistry = !loadModules
|
||||
? loadPluginManifestRegistryForPluginRegistry({
|
||||
config: rawConfig,
|
||||
env: params?.env,
|
||||
workspaceDir: initialWorkspaceDir,
|
||||
includeDisabled: true,
|
||||
})
|
||||
: undefined;
|
||||
const baseContext = resolvePluginRuntimeLoadContext({
|
||||
config: params?.config ?? getRuntimeConfig(),
|
||||
config: rawConfig,
|
||||
env: params?.env,
|
||||
logger: params?.logger,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
workspaceDir: initialWorkspaceDir,
|
||||
manifestRegistry,
|
||||
});
|
||||
const workspaceDir = baseContext.workspaceDir ?? resolveDefaultAgentWorkspaceDir();
|
||||
const workspaceDir =
|
||||
baseContext.workspaceDir ?? initialWorkspaceDir ?? resolveDefaultAgentWorkspaceDir();
|
||||
const context =
|
||||
workspaceDir === baseContext.workspaceDir
|
||||
? baseContext
|
||||
@@ -273,7 +290,6 @@ function buildPluginReport(
|
||||
...baseContext,
|
||||
workspaceDir,
|
||||
};
|
||||
const rawConfig = context.rawConfig;
|
||||
const config = context.config;
|
||||
|
||||
// Apply bundled-provider allowlist compat so that `plugins list` and `doctor`
|
||||
@@ -286,6 +302,7 @@ function buildPluginReport(
|
||||
config,
|
||||
workspaceDir,
|
||||
env: params?.env,
|
||||
manifestRegistry,
|
||||
});
|
||||
const effectiveConfig = withBundledPluginAllowlistCompat({
|
||||
config,
|
||||
@@ -336,6 +353,8 @@ function buildPluginReport(
|
||||
logger: params?.logger,
|
||||
loadModules: false,
|
||||
onlyPluginIds,
|
||||
manifestRegistry,
|
||||
runtimeContext: context,
|
||||
}),
|
||||
{ surface: "status", onlyPluginCount: onlyPluginIds?.length },
|
||||
);
|
||||
@@ -376,14 +395,17 @@ export function buildPluginInspectReport(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
logger?: PluginLogger;
|
||||
report?: PluginStatusReport;
|
||||
resolvedConfig?: OpenClawConfig;
|
||||
}): PluginInspectReport | null {
|
||||
const rawConfig = params.config ?? getRuntimeConfig();
|
||||
const config = resolvePluginRuntimeLoadContext({
|
||||
config: rawConfig,
|
||||
env: params.env,
|
||||
logger: params.logger,
|
||||
workspaceDir: params.workspaceDir,
|
||||
}).config;
|
||||
const config =
|
||||
params.resolvedConfig ??
|
||||
resolvePluginRuntimeLoadContext({
|
||||
config: rawConfig,
|
||||
env: params.env,
|
||||
logger: params.logger,
|
||||
workspaceDir: params.workspaceDir,
|
||||
}).config;
|
||||
const report =
|
||||
params.report ??
|
||||
buildPluginDiagnosticsReport({
|
||||
@@ -508,6 +530,12 @@ export function buildAllPluginInspectReports(params?: {
|
||||
report?: PluginStatusReport;
|
||||
}): PluginInspectReport[] {
|
||||
const rawConfig = params?.config ?? getRuntimeConfig();
|
||||
const config = resolvePluginRuntimeLoadContext({
|
||||
config: rawConfig,
|
||||
env: params?.env,
|
||||
logger: params?.logger,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
}).config;
|
||||
const report =
|
||||
params?.report ??
|
||||
buildPluginDiagnosticsReport({
|
||||
@@ -523,6 +551,9 @@ export function buildAllPluginInspectReports(params?: {
|
||||
id: plugin.id,
|
||||
config: rawConfig,
|
||||
logger: params?.logger,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env: params?.env,
|
||||
resolvedConfig: config,
|
||||
report,
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user