feat: add openclaw-plugin-dev skill

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>
This commit is contained in:
zhi
2026-04-18 17:25:07 +00:00
parent 1d34768019
commit 5a8f490cc2
10 changed files with 465 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
# State Management
## Where to Store What
| Data Type | Location | Reason |
|-----------|----------|--------|
| Business state (maps, sets, caches) | `globalThis` | Module vars reset on hot reload |
| Event dedup (WeakSet/Set) | `globalThis` | Same |
| Gateway lifecycle flags | `globalThis` | Prevent double init |
| Connection flags (WebSocket/TCP) | `globalThis` | Prevent duplicate connections |
| Runtime references | `globalThis` | Closures need living instance |
| Cross-plugin API objects (`__pluginId`) | `globalThis` | Other plugins access via globalThis |
| Pure utility functions | Module-level | No state needed |
| Persistent data | File + memory cache | Survives gateway restart |
## Cross-Plugin API Pattern
### Provider side
```typescript
const _G = globalThis as Record<string, unknown>;
// Init shared objects once
if (!(_G["_myRegistry"] instanceof MyRegistry)) {
_G["_myRegistry"] = new MyRegistry();
}
// Overwrite public API every register() (updates closures)
_G["__myPlugin"] = {
registry: _G["_myRegistry"],
send: (msg) => (_G["_myRuntime"] as Runtime)?.send(msg) ?? false,
};
```
### Consumer side
```typescript
const provider = (globalThis as any)["__myPlugin"];
if (!provider) {
console.error("[consumer] __myPlugin not found");
return;
}
provider.registry.register("my_rule", handler);
```
### Load Order
Provider must be listed before consumer in `plugins.allow`. Consumer must defend against provider not being loaded.