7.0 KiB
Multi-Transport Plugin Contexts
Summary
Introduce a host-side transport-aware context factory that provides one Eventa context per plugin instance. Plugin SDK APIs become context-bound factories, allowing local (in-memory/worker) and remote (WebSocket) plugins to share the same API surface while using different transports. This enables multiple plugins within a single Plugin Host without cross-talk or global channel coupling.
Background
plugin-sdk currently exposes APIs (for example providers.listProviders) that call defineInvoke on a globally imported channel. This couples plugins to a single shared context and prevents the host from isolating multiple plugins or using different transports per plugin. We also need a path to support local plugins (in-process or worker) and remote plugins (WebSocket) with consistent ergonomics.
Eventa is context-oriented: contexts are created per transport (in-memory, WebSocket, worker, electron) and the invoke/handler APIs attach to that context. Multiple contexts can co-exist in the same process.
Goals
- Provide one context per plugin instance, scoped by transport.
- Allow the same API surface to work for local and remote plugins.
- Keep transport selection under Plugin Host control, not plugin control.
- Support multiple plugins within one host without channel conflicts.
- Keep the API ergonomics for plugin authors simple and explicit.
Non-goals
- Designing the full plugin lifecycle orchestration (phase transitions, capability config, etc.).
- Implementing a new transport stack beyond Eventa adapters (unless required by runtime gaps).
- Defining plugin packaging or distribution formats beyond
ManifestV1entrypoints.
Proposal
- Introduce a host-side
createPluginContext(transport)factory that returns an Eventa context bound to the plugin's transport. - Convert plugin SDK APIs to context-bound factories (
createApis(ctx)), replacing global channel usage. - Resolve transport per plugin instance during host setup and pass the created context into plugin
init(). - Add runtime-specific implementations under
plugin-host/runtimes/nodeandplugin-host/runtimes/webto handle different transport adapters. - Optional: introduce shared reliable WebSocket helpers if needed, but prefer Eventa adapters first.
Design Detials
Transport-aware contexts for isolated multi-plugin hosts.
Context And Transport Model
Define a small transport config type owned by the Plugin Host:
export type PluginTransport
= | { kind: 'in-memory' }
| { kind: 'websocket', url: string, protocols?: string[] }
| { kind: 'web-worker', worker: Worker }
| { kind: 'node-worker', worker: import('node:worker_threads').Worker }
| { kind: 'electron', target: 'main' | 'renderer', webContentsId?: number }
createPluginContext(transport) creates and returns an Eventa context based on the transport adapter (in-memory, WebSocket, worker, electron).
Lifecycle Placement
Context creation happens during host setup, before any plugin lifecycle method is called.
- Load plugin module (FileSystemLoader / UrlLoader).
- Resolve transport for the plugin (manifest + host config).
- Create context via
createPluginContext(transport). - Bind APIs with
createApis(ctx). - Call
plugin.init({ host: ctx, apis }).
Host Runtime Layout
packages/plugin-sdk/src/plugin-host/transports/:- transport type definitions and helpers
packages/plugin-sdk/src/plugin-host/runtimes/node/:- in-memory, node-worker, websocket implementations
packages/plugin-sdk/src/plugin-host/runtimes/web/:- web-worker, websocket implementations
packages/plugin-sdk/src/plugin-host/index.ts:- exports the runtime-appropriate
createPluginContextvia conditional exports
- exports the runtime-appropriate
API Binding Strategy
Replace direct channel usage with context-bound factories:
export function createProviders(ctx: EventaContext) {
return {
listProviders() {
return defineInvoke(ctx, protocolListProviders)()
},
}
}
export function createApis(ctx: EventaContext) {
return { providers: createProviders(ctx) }
}
Plugins call createApis(ctx) provided by the host instead of importing global singletons.
Local Vs Remote Plugins
- Local plugins:
in-memoryfor simplest casenode-workerorweb-workerfor isolation
- Remote plugins:
websockettransport bound to a specific URL or connection
Transport selection is a host concern; plugins are transport-agnostic.
Multi-Plugin Isolation
Each plugin has its own context and transport. APIs are bound to that context, preventing cross-talk. The host keeps a registry mapping plugin ID to its context, transport, and loaded module for lifecycle management.
Verify & Test
Criteria
- Multiple plugins can be loaded in one host without shared global channels.
- Local plugin calls use in-memory or worker contexts without manual wiring in plugin code.
- Remote plugin calls use WebSocket contexts and do not affect local plugins.
- Existing plugin tests can be updated to pass by injecting a context into APIs.
Test & QA
- Unit test: create two plugin contexts in the same process, verify isolated invoke/handler pairs.
- Unit test: FileSystemLoader + in-memory context binds correctly to
createApis(ctx). - Integration test (optional): WebSocket adapter roundtrip using a stub server.
Progress
Status
Planned.
Next Steps
- Implement
createApis(ctx)and migrate current API modules. - Implement
createPluginContextin node runtime (in-memory + websocket). - Update tests to construct APIs with a provided context.
Reviews
Q&A
-
Q: Why not keep global channels and just switch the active channel? A: Global channels make multi-plugin isolation impossible and require global state mutation. Context-per-plugin avoids cross-talk and matches Eventa's design.
-
Q: Do plugins need to know about transports? A: No. The host injects the context and APIs; plugins remain transport-agnostic.
-
Q: Should we build a shared reliable WebSocket package? A: Only if we need custom reconnection/heartbeat logic across multiple packages. Start with Eventa adapters; factor out shared logic later if required.
-
Q: Can workers be used for plugin isolation? A: Yes. Use Eventa web-worker or node-worker adapters to bridge a per-plugin context to the worker.