Files
openclaw/src/cli/plugins-registry-refresh.ts
Peter Steinberger d678bcfcc7 fix: hot reload plugin management changes (#75976)
Summary:
- The PR changes Gateway reload planning, CLI plugin install-index writes, plugin runtime/cache cleanup, docs, changelog, and tests so plugin enable/disable hot reloads while install/update/uninstall stay restart-backed.
- Reproducibility: yes. The earlier blocker has a source-level reproduction: run an external plugin install/up ...  watches config and only the managed plugin index changes; the PR now tests that path and queues a restart.

ClawSweeper fixups:
- Included follow-up commit: fix: hot reload plugin management changes
- Included follow-up commit: fix(clawsweeper): address review for automerge-openclaw-openclaw-7597…
- Ran the ClawSweeper repair loop before final review.

Validation:
- ClawSweeper review passed for head 860594f722.
- Required merge gates passed before the squash merge.

Prepared head SHA: 860594f722
Review: https://github.com/openclaw/openclaw/pull/75976#issuecomment-4363168379

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-02 13:19:24 +00:00

59 lines
2.3 KiB
TypeScript

import type { OpenClawConfig } from "../config/types.openclaw.js";
import { formatErrorMessage } from "../infra/errors.js";
import { loadInstalledPluginIndexInstallRecords } from "../plugins/installed-plugin-index-records.js";
import type { InstalledPluginIndexRefreshReason } from "../plugins/installed-plugin-index.js";
import { tracePluginLifecyclePhaseAsync } from "../plugins/plugin-lifecycle-trace.js";
import { refreshPluginRegistry } from "../plugins/plugin-registry.js";
export type PluginRegistryRefreshLogger = {
warn?: (message: string) => void;
};
export async function refreshPluginRegistryAfterConfigMutation(params: {
config: OpenClawConfig;
reason: InstalledPluginIndexRefreshReason;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
installRecords?: Awaited<ReturnType<typeof loadInstalledPluginIndexInstallRecords>>;
policyPluginIds?: readonly string[];
traceCommand?: string;
logger?: PluginRegistryRefreshLogger;
}): Promise<void> {
try {
const installRecords =
params.installRecords ??
(await tracePluginLifecyclePhaseAsync(
"install records load",
() => loadInstalledPluginIndexInstallRecords(params.env ? { env: params.env } : {}),
{ command: params.traceCommand ?? "registry-refresh" },
));
await tracePluginLifecyclePhaseAsync(
"registry refresh",
() =>
refreshPluginRegistry({
config: params.config,
reason: params.reason,
installRecords,
...(params.policyPluginIds ? { policyPluginIds: params.policyPluginIds } : {}),
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
...(params.env ? { env: params.env } : {}),
}),
{ command: params.traceCommand ?? "registry-refresh", reason: params.reason },
);
} catch (error) {
params.logger?.warn?.(`Plugin registry refresh failed: ${formatErrorMessage(error)}`);
}
await invalidatePluginRuntimeDiscoveryAfterConfigMutation(params);
}
async function invalidatePluginRuntimeDiscoveryAfterConfigMutation(params: {
logger?: PluginRegistryRefreshLogger;
}): Promise<void> {
try {
const { clearPluginRegistryLoadCache } = await import("../plugins/loader.js");
clearPluginRegistryLoadCache();
} catch (error) {
params.logger?.warn?.(`Plugin runtime cache invalidation failed: ${formatErrorMessage(error)}`);
}
}