mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
fix(plugins): restore cached command registries
This commit is contained in:
@@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Discord/gateway: prevent startup from getting stuck at `awaiting gateway readiness` when Carbon gateway registration races with a lifecycle reconnect. Fixes #52372. (#68159) Thanks @IVY-AI-gif.
|
||||
- Plugins/cache: restore plugin command and interactive handler registries on loader cache hits, so cached external plugins keep slash commands and callback handlers available after reloads. Fixes #71100. Thanks @BomBastikDE.
|
||||
- Plugin SDK/tool-result transforms: restrict harness tool-result middleware to bundled plugins, fail closed on middleware errors, validate rewritten result shapes, preserve Pi per-call ids, and keep Codex media trust checks anchored to raw tool provenance. Thanks @vincentkoc.
|
||||
- Gateway/MCP loopback: apply owner-only tool policy and run before-tool-call hooks on `127.0.0.1/mcp` `tools/list` and `tools/call`, so non-owner bearer callers can no longer see or invoke owner-only tools such as `cron`, `gateway`, and `nodes`, matching the existing HTTP `/tools/invoke` and embedded-agent paths. (#71159) Thanks @mmaps.
|
||||
- Codex harness/security: wait for final app-server approval decisions and sanitize approval preview text, so native Codex permission prompts cannot be resolved by an early placeholder decision or render unsafe terminal/control content. (#70751, #70569) Thanks @Lucenx9.
|
||||
|
||||
@@ -51,6 +51,21 @@ export function clearPluginCommandsForPlugin(pluginId: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function listRegisteredPluginCommands(): RegisteredPluginCommand[] {
|
||||
return Array.from(pluginCommands.values());
|
||||
}
|
||||
|
||||
export function restorePluginCommands(commands: readonly RegisteredPluginCommand[]): void {
|
||||
pluginCommands.clear();
|
||||
for (const command of commands) {
|
||||
const name = normalizeOptionalLowercaseString(command.name);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
pluginCommands.set(`/${name}`, command);
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePluginNativeName(
|
||||
command: OpenClawPluginCommandDefinition,
|
||||
provider?: string,
|
||||
|
||||
@@ -70,3 +70,25 @@ export function clearPluginInteractiveHandlersForPlugin(pluginId: string): void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function listPluginInteractiveHandlers(): RegisteredInteractiveHandler[] {
|
||||
return Array.from(getPluginInteractiveHandlersState().values());
|
||||
}
|
||||
|
||||
export function restorePluginInteractiveHandlers(
|
||||
registrations: readonly RegisteredInteractiveHandler[],
|
||||
): void {
|
||||
clearPluginInteractiveHandlers();
|
||||
const interactiveHandlers = getPluginInteractiveHandlersState();
|
||||
for (const registration of registrations) {
|
||||
const namespace = normalizePluginInteractiveNamespace(registration.namespace);
|
||||
if (!namespace) {
|
||||
continue;
|
||||
}
|
||||
interactiveHandlers.set(toPluginInteractiveRegistryKey(registration.channel, namespace), {
|
||||
...registration,
|
||||
namespace,
|
||||
channel: normalizeOptionalLowercaseString(registration.channel) ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3178,6 +3178,64 @@ module.exports = { id: "throws-after-import", register() {} };`,
|
||||
expect(getDetachedTaskLifecycleRuntimeRegistration()?.pluginId).toBe("cached-detached-runtime");
|
||||
});
|
||||
|
||||
it("restores cached command and interactive handler registrations on cache hits", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "cached-command-interactive",
|
||||
filename: "cached-command-interactive.cjs",
|
||||
body: `module.exports = {
|
||||
id: "cached-command-interactive",
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "hue",
|
||||
description: "Control Hue lights",
|
||||
handler: async () => ({ text: "ok" }),
|
||||
});
|
||||
api.registerInteractiveHandler({
|
||||
channel: "telegram",
|
||||
namespace: "hue",
|
||||
handle: async () => ({ handled: true }),
|
||||
});
|
||||
},
|
||||
};`,
|
||||
});
|
||||
|
||||
const loadOptions = {
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["cached-command-interactive"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["cached-command-interactive"],
|
||||
} satisfies Parameters<typeof loadOpenClawPlugins>[0];
|
||||
|
||||
loadOpenClawPlugins(loadOptions);
|
||||
expect(getPluginCommandSpecs()).toEqual([
|
||||
{ name: "hue", description: "Control Hue lights", acceptsArgs: false },
|
||||
]);
|
||||
expect(resolvePluginInteractiveNamespaceMatch("telegram", "hue:on")).toBeDefined();
|
||||
|
||||
clearPluginCommands();
|
||||
clearPluginInteractiveHandlers();
|
||||
expect(getPluginCommandSpecs()).toEqual([]);
|
||||
expect(resolvePluginInteractiveNamespaceMatch("telegram", "hue:on")).toBeNull();
|
||||
|
||||
loadOpenClawPlugins(loadOptions);
|
||||
|
||||
expect(getPluginCommandSpecs()).toEqual([
|
||||
{ name: "hue", description: "Control Hue lights", acceptsArgs: false },
|
||||
]);
|
||||
expect(
|
||||
resolvePluginInteractiveNamespaceMatch("telegram", "hue:on")?.registration,
|
||||
).toMatchObject({
|
||||
pluginId: "cached-command-interactive",
|
||||
namespace: "hue",
|
||||
channel: "telegram",
|
||||
});
|
||||
});
|
||||
|
||||
it("clears stale detached task runtime registrations on active reloads when no plugin re-registers one", () => {
|
||||
useNoBundledPlugins();
|
||||
registerDetachedTaskLifecycleRuntime("stale-runtime", createDetachedTaskRuntimeStub("stale"));
|
||||
|
||||
@@ -37,7 +37,11 @@ import {
|
||||
resolveBundledRuntimeDependencyInstallRoot,
|
||||
type BundledRuntimeDepsInstallParams,
|
||||
} from "./bundled-runtime-deps.js";
|
||||
import { clearPluginCommands } from "./command-registry-state.js";
|
||||
import {
|
||||
clearPluginCommands,
|
||||
listRegisteredPluginCommands,
|
||||
restorePluginCommands,
|
||||
} from "./command-registry-state.js";
|
||||
import {
|
||||
clearCompactionProviders,
|
||||
listRegisteredCompactionProviders,
|
||||
@@ -56,7 +60,11 @@ import {
|
||||
} from "./config-state.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { initializeGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { clearPluginInteractiveHandlers } from "./interactive-registry.js";
|
||||
import {
|
||||
clearPluginInteractiveHandlers,
|
||||
listPluginInteractiveHandlers,
|
||||
restorePluginInteractiveHandlers,
|
||||
} from "./interactive-registry.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginBundleFormat, PluginDiagnostic, PluginFormat } from "./manifest-types.js";
|
||||
@@ -205,6 +213,8 @@ export class PluginLoadReentryError extends Error {
|
||||
type CachedPluginState = {
|
||||
registry: PluginRegistry;
|
||||
detachedTaskRuntimeRegistration: ReturnType<typeof getDetachedTaskLifecycleRuntimeRegistration>;
|
||||
commands: ReturnType<typeof listRegisteredPluginCommands>;
|
||||
interactiveHandlers: ReturnType<typeof listPluginInteractiveHandlers>;
|
||||
memoryCapability: ReturnType<typeof getMemoryCapabilityRegistration>;
|
||||
memoryCorpusSupplements: ReturnType<typeof listMemoryCorpusSupplements>;
|
||||
agentHarnesses: ReturnType<typeof listRegisteredAgentHarnesses>;
|
||||
@@ -243,8 +253,10 @@ export function clearPluginLoaderCache(): void {
|
||||
openAllowlistWarningCache.clear();
|
||||
clearBundledRuntimeDependencyNodePaths();
|
||||
clearAgentHarnesses();
|
||||
clearPluginCommands();
|
||||
clearCompactionProviders();
|
||||
clearDetachedTaskLifecycleRuntimeRegistration();
|
||||
clearPluginInteractiveHandlers();
|
||||
clearMemoryEmbeddingProviders();
|
||||
clearMemoryPluginState();
|
||||
}
|
||||
@@ -1871,8 +1883,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
const cached = getCachedPluginRegistry(cacheKey);
|
||||
if (cached) {
|
||||
restoreRegisteredAgentHarnesses(cached.agentHarnesses);
|
||||
restorePluginCommands(cached.commands);
|
||||
restoreRegisteredCompactionProviders(cached.compactionProviders);
|
||||
restoreDetachedTaskLifecycleRuntimeRegistration(cached.detachedTaskRuntimeRegistration);
|
||||
restorePluginInteractiveHandlers(cached.interactiveHandlers);
|
||||
restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
capability: cached.memoryCapability,
|
||||
@@ -2825,7 +2839,9 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
|
||||
if (cacheEnabled) {
|
||||
setCachedPluginRegistry(cacheKey, {
|
||||
commands: listRegisteredPluginCommands(),
|
||||
detachedTaskRuntimeRegistration: getDetachedTaskLifecycleRuntimeRegistration(),
|
||||
interactiveHandlers: listPluginInteractiveHandlers(),
|
||||
memoryCapability: getMemoryCapabilityRegistration(),
|
||||
memoryCorpusSupplements: listMemoryCorpusSupplements(),
|
||||
registry,
|
||||
|
||||
Reference in New Issue
Block a user