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>
129 lines
3.4 KiB
Markdown
129 lines
3.4 KiB
Markdown
# 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:
|
|
|
|
```typescript
|
|
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 object
|
|
- `agent_end` → Set on runId with size cap 500
|
|
- `gateway_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`):
|
|
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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.
|