mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(plugins): warn when registerHook uses a typed hook event name (#118601)
* [AI] fix(plugins): warn when registerHook uses a typed hook event name api.registerHook registers into the legacy internal-hook path, while typed lifecycle events (before_tool_call, message_received, ...) are dispatched exclusively by the typed hook runner. Registrations under typed names silently never fired since #117372 moved the write to legacyInternalHooks. Emit a registration diagnostic pointing to the public api.on(...) API so plugin authors see the no-op at load time instead of trusting a false "loaded". Related to #116965 Co-Authored-By: deepseek-v4-flash <noreply@anthropic.com> * [AI] fix(plugins): point registerHook comment at public api.on API Condense the inline comment in createToolHookRegistrars and replace the internal registerTypedHook migration reference with the public api.on(...) API, matching the emitted warning and hooks docs. Behavior and tests unchanged. --------- Co-authored-by: deepseek-v4-flash <noreply@anthropic.com> Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
This commit is contained in:
@@ -310,6 +310,14 @@ coarse command/lifecycle event system and show up in `openclaw hooks list` as
|
||||
`plugin:<id>`. Use those for side effects and compatibility with hook packs, not
|
||||
for ordered middleware or policy gates.
|
||||
|
||||
The legacy Plugin SDK `api.registerHook` registers into the internal event
|
||||
system only (`command:new`, `gateway:startup`, `message:received`, ...). Typed
|
||||
lifecycle event names such as `before_tool_call`, `message_received`, or
|
||||
`session_start` are dispatched exclusively by the typed hook runner and are
|
||||
**not** invoked through `registerHook`. Registering a typed name with
|
||||
`registerHook` emits a registration warning pointing to the public `api.on(...)`
|
||||
API as the replacement; it never silently no-ops.
|
||||
|
||||
For the complete plugin hook reference, see [Plugin hooks](/plugins/hooks).
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -116,6 +116,89 @@ describe("loadOpenClawPlugins", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("warns when registerHook is used with a typed hook event name", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "typed-name-legacy-register",
|
||||
filename: "typed-name-legacy-register.cjs",
|
||||
body: `module.exports = {
|
||||
id: "typed-name-legacy-register",
|
||||
register(api) {
|
||||
api.registerHook(["before_tool_call", "message_received"], () => {}, {
|
||||
name: "typed-name-legacy-register",
|
||||
});
|
||||
},
|
||||
};`,
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["typed-name-legacy-register"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["typed-name-legacy-register"],
|
||||
});
|
||||
|
||||
expect(registry.legacyInternalHooks.map((entry) => entry.event)).toEqual([
|
||||
"before_tool_call",
|
||||
"message_received",
|
||||
]);
|
||||
expect(
|
||||
registry.diagnostics
|
||||
.filter(
|
||||
(diagnostic) =>
|
||||
diagnostic.pluginId === "typed-name-legacy-register" &&
|
||||
diagnostic.level === "warn" &&
|
||||
diagnostic.message.includes("dispatched by the typed hook runner only"),
|
||||
)
|
||||
.map((diagnostic) => diagnostic.message),
|
||||
).toEqual([
|
||||
expect.stringContaining('Use api.on("before_tool_call", ...)'),
|
||||
expect.stringContaining('Use api.on("message_received", ...)'),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps legacy type:action events diagnostic-free in registerHook", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "legacy-type-action-register",
|
||||
filename: "legacy-type-action-register.cjs",
|
||||
body: `module.exports = {
|
||||
id: "legacy-type-action-register",
|
||||
register(api) {
|
||||
api.registerHook("gateway:startup", () => {}, { name: "legacy-startup" });
|
||||
api.registerHook("command:new", () => {}, { name: "legacy-command" });
|
||||
},
|
||||
};`,
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["legacy-type-action-register"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["legacy-type-action-register"],
|
||||
});
|
||||
|
||||
expect(registry.legacyInternalHooks.map((entry) => entry.event)).toEqual([
|
||||
"gateway:startup",
|
||||
"command:new",
|
||||
]);
|
||||
expect(
|
||||
registry.diagnostics.some((diagnostic) =>
|
||||
diagnostic.message.includes("dispatched by the typed hook runner only"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("runs consecutive plugin hook handlers with shared mutable context but isolated plugin config", async () => {
|
||||
useNoBundledPlugins();
|
||||
const first = writePlugin({
|
||||
|
||||
@@ -312,6 +312,22 @@ export function createToolHookRegistrars(state: PluginRegistryState) {
|
||||
pluginConfig: unknown,
|
||||
) => {
|
||||
const normalizedEvents = normalizeStringEntries(Array.isArray(events) ? events : [events]);
|
||||
// Typed lifecycle names (before_tool_call, message_received, ...) are dispatched only by
|
||||
// the typed hook runner; registerHook uses the legacy internal-hook path so they never
|
||||
// fire. Warn so authors move to `api.on(...)` instead of trusting a false "loaded".
|
||||
for (const event of normalizedEvents) {
|
||||
if (isPluginHookName(event)) {
|
||||
pushDiagnostic({
|
||||
level: "warn",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message:
|
||||
`hook event "${event}" is dispatched by the typed hook runner only; ` +
|
||||
`api.registerHook registrations for it are not invoked. ` +
|
||||
`Use api.on("${event}", ...) instead.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
const entry = opts?.entry ?? null;
|
||||
const hookName = entry?.hook.name ?? opts?.name?.trim();
|
||||
if (!hookName) {
|
||||
|
||||
Reference in New Issue
Block a user