mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(cli): skip plugin loader cache clear on short-lived commands
This commit is contained in:
@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Codex/providers/models: release session write locks when prompt-release fence reads fail, retire abandoned Codex app-server startups, keep stream-to-parent ACP spawns registered, close Codex startup clients on timeout, recover bundled provider aliases, avoid custom-provider runtime fanout, preserve provider prompt-cache boundaries, forward Gemini stop sequences, and strip Kimi-incompatible Anthropic cache markers. (#89811) Thanks @takhoffman.
|
||||
- Memory/build/update: warn after startup watcher pressure checks, externalize optional Baileys image backends, restore and pin Canvas A2UI compatibility assets, keep plugin repair fetch failures nonblocking, restore Skill Workshop view switching, and keep the current chat toggle active after awaited session switches. (#89244) Thanks @RomneyDa.
|
||||
- Plugins/auth: keep Hermes migration reports pointed at SQLite auth-profile stores and keep plugin auth-profile reuse tests on the current store path.
|
||||
- Plugins/CLI: avoid importing the runtime plugin loader only to clear in-process caches after short-lived plugin install, enable, disable, update, and uninstall commands refresh registry metadata.
|
||||
- Security/config/tooling: reject corrupt shell snapshots, suspicious gateway startup configs, malformed release/test/tooling/Docker/perf numeric limits, oversized audit responses, unsafe exec precheck env, and invalid pending-agent SQLite scaffold denials. (#89701, #89705, #89480, #81488) Thanks @RomneyDa and @mmaps.
|
||||
- Release/CI/E2E: restore package changelog extraction after the post-2026.6.1 version bump, keep hydrated pnpm modules under `node_modules` for ARM/Linux package lifecycle scripts, keep OpenAI live-cache prerequisites advisory while Anthropic prerequisites stay blocking, retry Windows Parallels background log appends on transient file-lock errors, bound candidate GitHub and cross-OS Discord fetches, harden ARM smoke/browser checks, show Docker build heartbeats, reset Crabbox pnpm hydrate state, and isolate Testbox/Docker/release journey artifacts.
|
||||
- Release/CI/E2E: keep Crabbox hydrate pnpm stores on the persistent cache volume while still resetting volatile modules, reducing cold installs and runner memory churn.
|
||||
|
||||
@@ -551,7 +551,7 @@ export function registerHooksCli(program: Command): void {
|
||||
defaultRuntime.log(
|
||||
theme.warn("`openclaw hooks install` is deprecated; use `openclaw plugins install`."),
|
||||
);
|
||||
await runPluginInstallCommand({ raw, opts });
|
||||
await runPluginInstallCommand({ raw, opts, invalidateRuntimeCache: false });
|
||||
});
|
||||
|
||||
hooks
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import {
|
||||
applyExclusiveSlotSelection,
|
||||
buildPluginSnapshotReport,
|
||||
clearPluginRegistryLoadCache,
|
||||
enablePluginInConfig,
|
||||
findBundledPluginSourceMock,
|
||||
installHooksFromNpmSpec,
|
||||
@@ -563,6 +564,7 @@ describe("plugins cli install", () => {
|
||||
expect(replaceConfigCall().nextConfig).toBe(enabledCfg);
|
||||
expect(runtimeLogsContain("slot adjusted")).toBe(true);
|
||||
expect(runtimeLogsContain("Installed plugin: alpha")).toBe(true);
|
||||
expect(clearPluginRegistryLoadCache).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes force through as overwrite mode for marketplace installs", async () => {
|
||||
|
||||
@@ -208,6 +208,7 @@ export async function runPluginsEnableCommand(idInput: string): Promise<void> {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: next,
|
||||
reason: "policy-changed",
|
||||
invalidateRuntimeCache: false,
|
||||
policyPluginIds: [enableResult.pluginId],
|
||||
logger: {
|
||||
warn: (message) => defaultRuntime.log(theme.warn(message)),
|
||||
@@ -249,6 +250,7 @@ export async function runPluginsDisableCommand(idInput: string): Promise<void> {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: next,
|
||||
reason: "policy-changed",
|
||||
invalidateRuntimeCache: false,
|
||||
policyPluginIds: [id],
|
||||
logger: {
|
||||
warn: (message) => defaultRuntime.log(theme.warn(message)),
|
||||
@@ -265,7 +267,7 @@ export async function runPluginsInstallAction(
|
||||
"install command",
|
||||
async () => {
|
||||
const { runPluginInstallCommand } = await import("./plugins-install-command.js");
|
||||
await runPluginInstallCommand({ raw, opts });
|
||||
await runPluginInstallCommand({ raw, opts, invalidateRuntimeCache: false });
|
||||
},
|
||||
{ command: "install" },
|
||||
);
|
||||
|
||||
@@ -134,7 +134,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--dry-run", "Show what would be removed without making changes", false)
|
||||
.action(async (id: string, opts: PluginUninstallOptions) => {
|
||||
const { runPluginUninstallCommand } = await import("./plugins-uninstall-command.js");
|
||||
await runPluginUninstallCommand(id, opts);
|
||||
await runPluginUninstallCommand(id, { ...opts, invalidateRuntimeCache: false });
|
||||
});
|
||||
|
||||
plugins
|
||||
|
||||
@@ -149,6 +149,7 @@ async function installBundledPluginSource(params: {
|
||||
rawSpec: string;
|
||||
bundledSource: BundledPluginSource;
|
||||
warning: string;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
const existingEntry = params.snapshot.config.plugins?.entries?.[params.bundledSource.pluginId];
|
||||
@@ -175,6 +176,7 @@ async function installBundledPluginSource(params: {
|
||||
installPath: params.bundledSource.localPath,
|
||||
},
|
||||
enable: shouldEnable,
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
warningMessage: [params.warning, configWarning].filter(Boolean).join("\n"),
|
||||
runtime: params.runtime,
|
||||
});
|
||||
@@ -314,6 +316,7 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
expectedPluginId?: string;
|
||||
expectedIntegrity?: string;
|
||||
trustedSourceLinkedOfficialInstall?: boolean;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false }> {
|
||||
const result = await installPluginFromNpmSpec({
|
||||
@@ -345,6 +348,7 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
rawSpec: params.spec,
|
||||
bundledSource: bundledFallbackPlan.bundledSource,
|
||||
warning: bundledFallbackPlan.warning,
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
@@ -380,6 +384,7 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
snapshot: params.snapshot,
|
||||
pluginId: result.pluginId,
|
||||
install: installRecord,
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
@@ -391,6 +396,7 @@ async function tryInstallPluginFromNpmPackArchive(params: {
|
||||
archivePath: string;
|
||||
safetyOverrides: InstallSafetyOverrides;
|
||||
extensionsDir: string;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false }> {
|
||||
const result = await installPluginFromNpmPackArchive({
|
||||
@@ -428,6 +434,7 @@ async function tryInstallPluginFromNpmPackArchive(params: {
|
||||
...(result.npmResolution?.shasum ? { npmShasum: result.npmResolution.shasum } : {}),
|
||||
...(result.npmTarballName ? { npmTarballName: result.npmTarballName } : {}),
|
||||
},
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
@@ -439,6 +446,7 @@ async function tryInstallPluginFromGitSpec(params: {
|
||||
spec: string;
|
||||
safetyOverrides: InstallSafetyOverrides;
|
||||
extensionsDir: string;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false }> {
|
||||
const result = await installPluginFromGitSpec({
|
||||
@@ -466,6 +474,7 @@ async function tryInstallPluginFromGitSpec(params: {
|
||||
gitRef: result.git.ref,
|
||||
gitCommit: result.git.commit,
|
||||
},
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
@@ -573,11 +582,13 @@ export async function runPluginInstallCommand(params: {
|
||||
pin?: boolean;
|
||||
marketplace?: string;
|
||||
};
|
||||
invalidateRuntimeCache?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
assertConfigWriteAllowedInCurrentMode();
|
||||
|
||||
const runtime = params.runtime ?? defaultRuntime;
|
||||
const invalidateRuntimeCache = params.invalidateRuntimeCache ?? true;
|
||||
const shorthand = !params.opts.marketplace
|
||||
? await tracePluginLifecyclePhaseAsync(
|
||||
"marketplace shortcut resolution",
|
||||
@@ -685,6 +696,7 @@ export async function runPluginInstallCommand(params: {
|
||||
marketplaceSource: result.marketplaceSource,
|
||||
marketplacePlugin: result.marketplacePlugin,
|
||||
},
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
@@ -745,6 +757,7 @@ export async function runPluginInstallCommand(params: {
|
||||
installPath: resolved,
|
||||
version: probe.version,
|
||||
},
|
||||
invalidateRuntimeCache,
|
||||
successMessage: `Linked plugin path: ${shortenHomePath(resolved)}`,
|
||||
runtime,
|
||||
});
|
||||
@@ -787,6 +800,7 @@ export async function runPluginInstallCommand(params: {
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
},
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
@@ -819,6 +833,7 @@ export async function runPluginInstallCommand(params: {
|
||||
safetyOverrides,
|
||||
allowBundledFallback: false,
|
||||
extensionsDir,
|
||||
invalidateRuntimeCache,
|
||||
...(officialNpmTrust
|
||||
? {
|
||||
expectedPluginId: officialNpmTrust.pluginId,
|
||||
@@ -850,6 +865,7 @@ export async function runPluginInstallCommand(params: {
|
||||
archivePath: npmPackPath,
|
||||
safetyOverrides,
|
||||
extensionsDir,
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
if (!npmPackResult.ok) {
|
||||
@@ -865,6 +881,7 @@ export async function runPluginInstallCommand(params: {
|
||||
spec: raw,
|
||||
safetyOverrides,
|
||||
extensionsDir,
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
if (!gitResult.ok) {
|
||||
@@ -904,6 +921,7 @@ export async function runPluginInstallCommand(params: {
|
||||
rawSpec: raw,
|
||||
bundledSource: bundledPreNpmPlan.bundledSource,
|
||||
warning: bundledPreNpmPlan.warning,
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
}),
|
||||
{
|
||||
@@ -943,6 +961,7 @@ export async function runPluginInstallCommand(params: {
|
||||
expectedPluginId: officialExternalPlan.pluginId,
|
||||
expectedIntegrity: officialExternalPlan.expectedIntegrity,
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
if (!npmResult.ok) {
|
||||
@@ -973,6 +992,7 @@ export async function runPluginInstallCommand(params: {
|
||||
spec: raw,
|
||||
installPath: result.targetDir,
|
||||
},
|
||||
invalidateRuntimeCache,
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
@@ -990,6 +1010,7 @@ export async function runPluginInstallCommand(params: {
|
||||
safetyOverrides,
|
||||
allowBundledFallback: true,
|
||||
extensionsDir,
|
||||
invalidateRuntimeCache,
|
||||
...(officialNpmTrust
|
||||
? {
|
||||
expectedPluginId: officialNpmTrust.pluginId,
|
||||
|
||||
@@ -406,6 +406,41 @@ describe("persistPluginInstall", () => {
|
||||
expectRuntimeLogIncludes("Plugin registry refresh failed");
|
||||
});
|
||||
|
||||
it("skips runtime cache invalidation when the caller opts out", async () => {
|
||||
const { persistPluginInstall } = await import("./plugins-install-persist.js");
|
||||
const baseConfig = {
|
||||
plugins: {
|
||||
entries: {},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const enabledConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
alpha: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
enablePluginInConfig.mockReturnValue({ config: enabledConfig });
|
||||
|
||||
const next = await persistPluginInstall({
|
||||
snapshot: {
|
||||
config: baseConfig,
|
||||
baseHash: "config-1",
|
||||
},
|
||||
pluginId: "alpha",
|
||||
install: {
|
||||
source: "npm",
|
||||
spec: "alpha@1.0.0",
|
||||
installPath: "/tmp/alpha",
|
||||
},
|
||||
invalidateRuntimeCache: false,
|
||||
});
|
||||
|
||||
expect(next).toEqual(enabledConfig);
|
||||
expect(refreshPluginRegistry).toHaveBeenCalledTimes(1);
|
||||
expect(clearPluginRegistryLoadCache).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes stale denylist entries before enabling installed plugins", async () => {
|
||||
const { persistPluginInstall } = await import("./plugins-install-persist.js");
|
||||
const baseConfig = {
|
||||
|
||||
@@ -184,6 +184,7 @@ export async function persistPluginInstall(params: {
|
||||
pluginId: string;
|
||||
install: Omit<PluginInstallUpdate, "pluginId">;
|
||||
enable?: boolean;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
successMessage?: string;
|
||||
warningMessage?: string;
|
||||
runtime?: RuntimeEnv;
|
||||
@@ -260,6 +261,7 @@ export async function persistPluginInstall(params: {
|
||||
config: next,
|
||||
reason: "source-changed",
|
||||
installRecords: nextInstallRecords,
|
||||
invalidateRuntimeCache: params.invalidateRuntimeCache,
|
||||
traceCommand: "install",
|
||||
logger: {
|
||||
warn: (message) => runtime.log(theme.warn(message)),
|
||||
|
||||
@@ -18,6 +18,7 @@ export async function refreshPluginRegistryAfterConfigMutation(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
installRecords?: Awaited<ReturnType<typeof loadInstalledPluginIndexInstallRecords>>;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
policyPluginIds?: readonly string[];
|
||||
traceCommand?: string;
|
||||
logger?: PluginRegistryRefreshLogger;
|
||||
@@ -46,7 +47,9 @@ export async function refreshPluginRegistryAfterConfigMutation(params: {
|
||||
} catch (error) {
|
||||
params.logger?.warn?.(`Plugin registry refresh failed: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
await invalidatePluginRuntimeDiscoveryAfterConfigMutation(params);
|
||||
if (params.invalidateRuntimeCache !== false) {
|
||||
await invalidatePluginRuntimeDiscoveryAfterConfigMutation(params);
|
||||
}
|
||||
}
|
||||
|
||||
async function invalidatePluginRuntimeDiscoveryAfterConfigMutation(params: {
|
||||
|
||||
@@ -17,6 +17,7 @@ export type PluginUninstallOptions = {
|
||||
keepConfig?: boolean;
|
||||
force?: boolean;
|
||||
dryRun?: boolean;
|
||||
invalidateRuntimeCache?: boolean;
|
||||
};
|
||||
|
||||
function isPromptInputClosedError(
|
||||
@@ -194,6 +195,7 @@ export async function runPluginUninstallCommand(
|
||||
config: nextConfig,
|
||||
reason: "source-changed",
|
||||
installRecords: nextInstallRecords,
|
||||
invalidateRuntimeCache: opts.invalidateRuntimeCache,
|
||||
traceCommand: "uninstall",
|
||||
logger: {
|
||||
warn: (message) => runtime.log(theme.warn(message)),
|
||||
|
||||
@@ -142,6 +142,7 @@ export async function runPluginUpdateCommand(params: {
|
||||
config: nextConfig,
|
||||
reason: "source-changed",
|
||||
installRecords: nextPluginInstallRecords,
|
||||
invalidateRuntimeCache: false,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1869,6 +1869,7 @@ export async function updatePluginsAfterCoreUpdate(params: {
|
||||
reason: "source-changed",
|
||||
workspaceDir: params.root,
|
||||
installRecords: nextInstallRecords,
|
||||
invalidateRuntimeCache: false,
|
||||
logger: pluginLogger,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user