Plugin development reference and workflows based on real development experience (Dirigent, ContractorAgent, PrismFacet). Docs: structure, entry-point, hooks, tools, state, config, debugging Workflows: create-plugin, add-hook Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1.3 KiB
1.3 KiB
Plugin Entry Point
Format
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
const _G = globalThis as Record<string, unknown>;
const LIFECYCLE_KEY = "_myPluginGatewayLifecycleRegistered";
export default {
id: "my-plugin",
name: "My Plugin",
register(api: OpenClawPluginApi) {
const config = normalizeConfig(api);
// Gateway lifecycle: only once
if (!_G[LIFECYCLE_KEY]) {
_G[LIFECYCLE_KEY] = true;
// Start sidecars, init global resources
api.on("gateway_stop", () => { _G[LIFECYCLE_KEY] = false; });
}
// Agent session hooks: every register() call (dedup inside handler)
registerMyHook(api, config);
// Tools
registerMyTools(api, config);
api.logger.info("my-plugin: registered");
},
};
Why globalThis?
OpenClaw may hot-reload plugins. Module-level variables reset on reload, but globalThis persists. All mutable state must be on globalThis:
- Startup flags → prevent double initialization
- Dedup sets → prevent double hook execution
- Runtime references → keep connections alive across reloads
- Shared registries → preserve cross-plugin state
Naming Convention
_<pluginId>PluginXxx # Internal state (e.g., _prismFacetRouters)
__<pluginId> # Cross-plugin public API (e.g., __yonexusClient)