mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 18:26:51 +00:00
fix(agents): skip runtime plugin reload when a registry is already active
ensureRuntimePluginsLoaded rebuilt and activated a replacement plugin registry on every call site invocation (agent turns, cron isolated-agent runs, the gateway prewarm sidecar). Activating a replacement retires the active registry and runs its plugin host cleanup, which cron.remove()s persistent plugin-scheduled session turns before the replacement's async re-registration catches up — silently deleting the cron row with only a low-level 'cron service unavailable' WARN. Plugins using api.session.workflow.scheduleSessionTurn for recurring jobs lose their schedule on the next agent turn after boot. Guard with getActiveRuntimePluginRegistry(): when a registry is already active every caller's request is satisfied, so return before building a replacement.
This commit is contained in:
@@ -8,12 +8,17 @@ const hoisted = vi.hoisted(() => ({
|
||||
() => "default",
|
||||
),
|
||||
getActivePluginRegistryWorkspaceDir: vi.fn<() => string | undefined>(() => undefined),
|
||||
getActiveRuntimePluginRegistry: vi.fn<() => unknown>(() => null),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/current-plugin-metadata-snapshot.js", () => ({
|
||||
getCurrentPluginMetadataSnapshot: hoisted.getCurrentPluginMetadataSnapshot,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/active-runtime-registry.js", () => ({
|
||||
getActiveRuntimePluginRegistry: hoisted.getActiveRuntimePluginRegistry,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/runtime/standalone-runtime-registry-loader.js", () => ({
|
||||
ensureStandaloneRuntimePluginRegistryLoaded: hoisted.ensureStandaloneRuntimePluginRegistryLoaded,
|
||||
}));
|
||||
@@ -36,6 +41,8 @@ describe("ensureRuntimePluginsLoaded", () => {
|
||||
hoisted.getActivePluginRuntimeSubagentMode.mockReturnValue("default");
|
||||
hoisted.getActivePluginRegistryWorkspaceDir.mockReset();
|
||||
hoisted.getActivePluginRegistryWorkspaceDir.mockReturnValue(undefined);
|
||||
hoisted.getActiveRuntimePluginRegistry.mockReset();
|
||||
hoisted.getActiveRuntimePluginRegistry.mockReturnValue(null);
|
||||
vi.resetModules();
|
||||
({ ensureRuntimePluginsLoaded } = await import("./runtime-plugins.js"));
|
||||
});
|
||||
@@ -52,6 +59,19 @@ describe("ensureRuntimePluginsLoaded", () => {
|
||||
expect(hoisted.ensureStandaloneRuntimePluginRegistryLoaded).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("skips loading entirely when a runtime plugin registry is already active", () => {
|
||||
hoisted.getActiveRuntimePluginRegistry.mockReturnValue({});
|
||||
|
||||
ensureRuntimePluginsLoaded({
|
||||
config: {} as never,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
allowGatewaySubagentBinding: true,
|
||||
});
|
||||
|
||||
expect(hoisted.getCurrentPluginMetadataSnapshot).not.toHaveBeenCalled();
|
||||
expect(hoisted.ensureStandaloneRuntimePluginRegistryLoaded).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves runtime plugins through the shared runtime helper", () => {
|
||||
ensureRuntimePluginsLoaded({
|
||||
config: {} as never,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* plugin IDs from metadata scope the load when available.
|
||||
*/
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { getActiveRuntimePluginRegistry } from "../plugins/active-runtime-registry.js";
|
||||
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { getActivePluginRuntimeSubagentMode } from "../plugins/runtime.js";
|
||||
@@ -41,6 +42,13 @@ export function ensureRuntimePluginsLoaded(params: {
|
||||
if (params.config && !normalizePluginsConfig(params.config.plugins).enabled) {
|
||||
return;
|
||||
}
|
||||
// Activating a replacement registry retires the active one and runs its
|
||||
// plugin host cleanup, which cron.remove()s persistent plugin-scheduled
|
||||
// jobs the replacement hasn't re-registered yet (async, still in flight).
|
||||
// Once a registry is active there is nothing left for this call to do.
|
||||
if (getActiveRuntimePluginRegistry()) {
|
||||
return;
|
||||
}
|
||||
const workspaceDir =
|
||||
typeof params.workspaceDir === "string" && params.workspaceDir.trim()
|
||||
? resolveUserPath(params.workspaceDir)
|
||||
|
||||
Reference in New Issue
Block a user