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.4 KiB
1.4 KiB
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
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
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.