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>
49 lines
1.4 KiB
Markdown
49 lines
1.4 KiB
Markdown
# 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.
|