chore: remove dead plugin hook loader

This commit is contained in:
Peter Steinberger
2026-02-22 08:45:15 +01:00
parent 185fba1d22
commit 121d027229
2 changed files with 14 additions and 123 deletions

View File

@@ -330,22 +330,29 @@ Plugins export either:
## Plugin hooks
Plugins can ship hooks and register them at runtime. This lets a plugin bundle
event-driven automation without a separate hook pack install.
Plugins can register hooks at runtime. This lets a plugin bundle event-driven
automation without a separate hook pack install.
### Example
```
import { registerPluginHooksFromDir } from "openclaw/plugin-sdk";
```ts
export default function register(api) {
registerPluginHooksFromDir(api, "./hooks");
api.registerHook(
"command:new",
async () => {
// Hook logic here.
},
{
name: "my-plugin.command-new",
description: "Runs when /new is invoked",
},
);
}
```
Notes:
- Hook directories follow the normal hook structure (`HOOK.md` + `handler.ts`).
- Register hooks explicitly via `api.registerHook(...)`.
- Hook eligibility rules still apply (OS/bins/env/config requirements).
- Plugin-managed hooks show up in `openclaw hooks list` with `plugin:<id>`.
- You cannot enable/disable plugin-managed hooks via `openclaw hooks`; enable/disable the plugin instead.

View File

@@ -1,116 +0,0 @@
import path from "node:path";
import { pathToFileURL } from "node:url";
import type { OpenClawPluginApi } from "../plugins/types.js";
import { shouldIncludeHook } from "./config.js";
import type { InternalHookHandler } from "./internal-hooks.js";
import type { HookEntry } from "./types.js";
import { loadHookEntriesFromDir } from "./workspace.js";
export type PluginHookLoadResult = {
hooks: HookEntry[];
loaded: number;
skipped: number;
errors: string[];
};
function resolveHookDir(api: OpenClawPluginApi, dir: string): string {
if (path.isAbsolute(dir)) {
return dir;
}
return path.resolve(path.dirname(api.source), dir);
}
function normalizePluginHookEntry(api: OpenClawPluginApi, entry: HookEntry): HookEntry {
return {
...entry,
hook: {
...entry.hook,
source: "openclaw-plugin",
pluginId: api.id,
},
metadata: {
...entry.metadata,
hookKey: entry.metadata?.hookKey ?? `${api.id}:${entry.hook.name}`,
events: entry.metadata?.events ?? [],
},
};
}
async function loadHookHandler(
entry: HookEntry,
api: OpenClawPluginApi,
): Promise<InternalHookHandler | null> {
try {
const url = pathToFileURL(entry.hook.handlerPath).href;
const cacheBustedUrl = `${url}?t=${Date.now()}`;
const mod = (await import(cacheBustedUrl)) as Record<string, unknown>;
const exportName = entry.metadata?.export ?? "default";
const handler = mod[exportName];
if (typeof handler === "function") {
return handler as InternalHookHandler;
}
api.logger.warn?.(`[hooks] ${entry.hook.name} handler is not a function`);
return null;
} catch (err) {
api.logger.warn?.(`[hooks] Failed to load ${entry.hook.name}: ${String(err)}`);
return null;
}
}
export async function registerPluginHooksFromDir(
api: OpenClawPluginApi,
dir: string,
): Promise<PluginHookLoadResult> {
const resolvedDir = resolveHookDir(api, dir);
const hooks = loadHookEntriesFromDir({
dir: resolvedDir,
source: "openclaw-plugin",
pluginId: api.id,
});
const result: PluginHookLoadResult = {
hooks,
loaded: 0,
skipped: 0,
errors: [],
};
for (const entry of hooks) {
const normalizedEntry = normalizePluginHookEntry(api, entry);
const events = normalizedEntry.metadata?.events ?? [];
if (events.length === 0) {
api.logger.warn?.(`[hooks] ${entry.hook.name} has no events; skipping`);
api.registerHook(events, async () => undefined, {
entry: normalizedEntry,
register: false,
});
result.skipped += 1;
continue;
}
const handler = await loadHookHandler(entry, api);
if (!handler) {
result.errors.push(`[hooks] Failed to load ${entry.hook.name}`);
api.registerHook(events, async () => undefined, {
entry: normalizedEntry,
register: false,
});
result.skipped += 1;
continue;
}
const eligible = shouldIncludeHook({ entry: normalizedEntry, config: api.config });
api.registerHook(events, handler, {
entry: normalizedEntry,
register: eligible,
});
if (eligible) {
result.loaded += 1;
} else {
result.skipped += 1;
}
}
return result;
}