Import types, HostContext and useHostContext() from @lomi-dev/plugin-sdk. This reference describes SDK 1.1.0-alpha.1 and host API 1 in the local Lomi source set. See Your first plugin for setup. The main SDK export requires the application runtime; do not execute it directly in Node.
Entry module
Section titled “Entry module”Export a named activate function from the module referenced by entry:
import type { PluginContext } from '@lomi-dev/plugin-sdk';
export function activate(context: PluginContext) { context.subscribe('context', (snapshot) => { console.log(snapshot.workspaceName); });}activate(context) may return nothing, a cleanup function, or a promise of either. The module may also export deactivate(): void | Promise<void>.
lazy activation starts when a declared view opens or a command runs. startup runs after the workbench is ready. Simultaneous triggers share one activation. Importing the package alone does not activate it, and safe startup prevents activation.
Runtime phases
Section titled “Runtime phases”| Phase | Meaning |
|---|---|
discovered |
A valid enabled package is waiting for activation |
incompatible |
Metadata or contract prevents use |
disabled |
Code is disabled, including during safe startup |
activating |
Host prepares the revision, loads CSS and the module, then awaits activate() |
active |
Activation succeeded |
failed |
Activation failed; inspect the error and retry option |
deactivating |
Host is releasing resources |
An enabled lazy plugin can remain discovered until used. Later command, timer or rendering errors may be recorded while the plugin remains active. Read status and error together.
Register contributions and prepare resources in activate(). Do not await your own command through executeCommand() there: command execution waits for activation to finish. Call it from a later user action. Avoid cyclic cross-plugin command calls during activation too.
PluginContext
Section titled “PluginContext”| Member | Contract |
|---|---|
id |
Read-only package ID. |
signal |
AbortSignal, aborted on deactivation or failed activation. |
snapshot() |
Returns the current HostSnapshot. |
registerView(id, component) |
Registers a declared React view receiving ViewProps. Returns Dispose. |
registerCommand(id, handler) |
Registers a declared () => void | Promise<void> command. Returns Dispose. |
registerFill(id, component) |
Registers a declared React component for an interface slot. Returns Dispose. |
registerDirtyView(panelId, view) |
Registers unsaved-work handling for a retained panel owned by this plugin. Returns Dispose. |
openView(id, state?) |
Opens one of this plugin’s declared views. Returns Promise<string> with its panel ID. Omitted state is null. |
executeCommand(id) |
Runs a declared plugin command through activation and context checks. Returns Promise<void>. |
subscribe(event, handler) |
Subscribes to a host event. Returns Dispose. |
interval(callback, milliseconds) |
Registers a timer from 50 to 2147483647 ms. Returns Dispose. |
add(dispose) |
Registers cleanup for a resource you own. Returns an idempotent Dispose. |
assetUrl(path) |
Resolves a relative asset path inside the installed package revision. |
Dispose is () => void. Call it when a registration is no longer needed; calling it again has no effect. The host also calls remaining disposers during deactivation. Registering the same contribution twice, registering undeclared IDs or using registration methods after deactivation throws.
openView() only opens views owned by the calling plugin and requires an open workspace. executeCommand() can address declared plugin commands, including commands from another plugin. Built-in application actions are not part of this public method’s contract.
Current context
Section titled “Current context”useHostContext() subscribes a React component to the same current context returned by snapshot():
| Field | Type and meaning |
|---|---|
projectPath |
string or null: active project folder. |
workspaceName |
string or null: active workspace name. |
activePanelId |
string or null: active panel, including a focused plugin sidebar. |
viewType |
string or null: built-in panel type or declared plugin view ID. |
appearance |
light or dark. |
themeRevision |
Number identifying the native theme source revision. |
This is the active host context, not a fixed project binding for each mounted view. Keep any document-specific path in your own retained model.
Events
Section titled “Events”| Event | Payload |
|---|---|
context |
HostSnapshot. |
theme |
{ revision: number, appearance: 'light' | 'dark' }. |
contributions |
{ owner: string }. |
The theme event uses a local application revision. It can change after an appearance preference or preview update without changing snapshot().themeRevision. These are not interchangeable counters. The event bus does not stream terminal output.
Subscribing does not immediately deliver the current snapshot. Read snapshot() to initialize, then use subscribe('context', ...) for changes. contributions reports completed owner activation or deactivation; it is not a file-change or panel-state event.
Cleanup and errors
Section titled “Cleanup and errors”The host aborts signal, runs registered disposers in reverse order and calls the optional module deactivate(). One cleanup error does not prevent the other registered cleanup functions from running.
Use context.interval() and context.subscribe() for host-managed resources. For a browser listener, add an explicit disposer:
const onResize = () => { // Update plugin-owned layout measurements here.};window.addEventListener('resize', onResize);context.add(() => window.removeEventListener('resize', onResize));This fragment belongs inside activate(). Resources created outside the context remain your responsibility. Handle your own rejected promises and event-handler errors. A React error boundary handles render failures, not arbitrary asynchronous work. An infinite loop blocks the main JavaScript thread; a promise timeout cannot stop it.
There is no public SDK filesystem, terminal, HTTP, notification or secret-storage service. Do not present internal host imports or native commands as stable SDK methods.
Asynchronous work
Section titled “Asynchronous work”Pass context.signal to operations supporting AbortSignal, such as browser fetch. After await, check context.signal.aborted before updating your model or registering more contributions. Handle expected cancellation without displaying it as a user-facing failure.
Intervals and subscriptions report callback errors, but an interval does not wait for its previous asynchronous run. Prevent overlapping work when an operation can take longer than the interval. context.add() accepts a synchronous Dispose; use module deactivate() for asynchronous shutdown.
Deactivation does not evict an ESM module. Module variables may survive reactivation of the same revision. Initialize and release them deliberately, and do not retain an old PluginContext with an aborted signal.
SDK exports for author tools
Section titled “SDK exports for author tools”| Import | Exports / purpose |
|---|---|
@lomi-dev/plugin-sdk |
Types: Json, Dispose, HostSnapshot, PanelDescriptor, ViewProps, DirtyView, CommandContext, PluginManifest, HostEvents, PluginContext, PluginModule; runtime: HostContext, useHostContext() |
@lomi-dev/plugin-sdk/manifest |
parsePlugin(value) returns a manifest or throws; packagePath(value) and jsonState(value) validate by throwing; also pluginId and validShortcut |
@lomi-dev/plugin-sdk/shortcuts |
validShortcut(value): boolean, ordered modifiers and keyNames |
@lomi-dev/plugin-sdk/compatibility |
compatibility and limits, including API, runtime, compatible versions and package limits |
@lomi-dev/plugin-sdk/build |
buildPlugin(options): Promise<string> returns the output directory; Node only |
@lomi-dev/plugin-sdk/package |
safePath(root, path), packageFiles(root), validatePackage(root); asynchronous Node file checks |
@lomi-dev/plugin-sdk/plugin.schema.json |
Editor schema |
@lomi-dev/plugin-sdk/contract-fixtures.json |
Compatibility cases for host and tool integrators |
@lomi-dev/plugin-sdk/package.json |
Installed package metadata |
packageFiles() returns a map of relative names to bytes. validatePackage() returns { manifest, files }. safePath() checks existence, symlinks and directory containment. These functions can reject their promises.
Manifest and shortcut validators do not require a running application. Inside plugin code, the bundler only exposes the main SDK runtime entry; import tooling subpaths from author scripts, not plugin runtime code. See the manual example for building without the CLI.