development: absorbed openclaw-plugin-dev as a workflow, with reference docs for plugin structure, hooks, tools, state, config. hf-hangman-lab: hf-wakeup workflow — full agent wakeup lifecycle: set busy → check due slots → select & defer → identify task → plan work → create work channel → execute. All branches end with status reset to idle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.4 KiB
OpenClaw Plugin Development
When creating or modifying an OpenClaw plugin.
Reference docs in
{baseDir}/docs/cover each topic in detail.
Process
1. Scaffold
Create the project structure:
my-plugin/
plugin/
index.ts # export default { id, name, register }
openclaw.plugin.json # config schema (additionalProperties: false)
package.json # name, version, type: module
hooks/ # one file per hook handler
tools/ # tool registrations
core/ # pure logic (no plugin-sdk imports)
scripts/
install.mjs # --install / --uninstall
package.json # dev dependencies
tsconfig.plugin.json
.gitignore
See {baseDir}/docs/structure.md for conventions.
2. Entry Point
Follow the globalThis lifecycle pattern:
const _G = globalThis as Record<string, unknown>;
const LIFECYCLE_KEY = "_myPluginLifecycleRegistered";
export default {
id: "my-plugin",
name: "My Plugin",
register(api) {
if (!_G[LIFECYCLE_KEY]) {
_G[LIFECYCLE_KEY] = true;
// gateway-level init (once)
api.on("gateway_stop", () => { _G[LIFECYCLE_KEY] = false; });
}
// agent-level hooks (every register call, dedup inside)
registerMyHook(api);
registerMyTools(api);
},
};
See {baseDir}/docs/entry-point.md for details.
3. Hooks
Each hook in its own file under hooks/. Must use dedup on globalThis:
before_model_resolve,before_prompt_build→ WeakSet on event objectagent_end→ Set on runId with size cap 500gateway_start/stop→ globalThis flag
If returning prependSystemContext/appendSystemContext, set allowPromptInjection: true in config.
See {baseDir}/docs/hooks.md.
4. Tools
Interface is inputSchema + execute (not parameters + handler):
api.registerTool({
name: "my-tool",
inputSchema: { type: "object", properties: { ... }, required: [...] },
execute: async (toolCallId, params) => { return { result: "ok" }; },
});
For agent context access, use factory form: api.registerTool((ctx) => ({ ... })).
See {baseDir}/docs/tools.md.
5. State Management
All mutable state on globalThis, not module-level variables (hot reload resets modules).
- Business state, dedup sets, lifecycle flags →
globalThis - Cross-plugin API →
globalThis.__pluginId - Pure functions → module-level is fine
See {baseDir}/docs/state.md.
6. Config Schema
openclaw.plugin.json must have additionalProperties: false. Every config field in install script must exist in schema. Never set sensitive fields (tokens) in install script.
See {baseDir}/docs/config.md.
7. Install Script
node scripts/install.mjs --install # build, copy to ~/.openclaw/plugins/<id>, update config
node scripts/install.mjs --uninstall # remove from config and filesystem
See {baseDir}/docs/config.md.
8. Build & Test
npm run build
node scripts/install.mjs --install
openclaw gateway restart
openclaw logs --follow
9. Checklist Before Deploy
- Hook handlers have dedup on globalThis
- Gateway lifecycle protected by globalThis flag
- Business state on globalThis
- Schema matches actual config fields
- Install script uses setIfMissing, no sensitive fields
- Clean dist before copy (
rmSync)
See {baseDir}/docs/debugging.md for full checklist.