Community Plugin Host Boundary
This page defines the approved host boundary for community plugin execution across browser/PWA and Electron desktop runtimes. It expands the host-model direction in Plugin Runtime without changing the current Obsidian-compatible plugin folder format.
Scope
The boundary applies to community plugins discovered under /.obsidian/plugins/<id>/. Bundled first-party plugins remain renderer-local and continue to use the normal PluginManager lifecycle.
The current renderer implementation remains the compatibility fallback. PluginManager now routes community plugin evaluation through a runtime-neutral execution-host seam, with the renderer host as the default implementation. The Electron sidecar path is the approved desktop target, but it must stay capability-advertised as unavailable until the IPC protocol, sidecar process manager, and API facades are implemented together.
That fallback remains the trusted DOM compatibility host for existing Obsidian-style plugins. New Lapis-native extension surfaces should prefer brokered capabilities plus isolated hosts or UI containers so the app can make trust and ownership boundaries explicit.
Ownership Model
PluginManager remains the lifecycle authority in every runtime. It owns discovery, manifest preflight, enable/disable state, boot ordering, rollback, and user-facing failure reporting.
A selected community-plugin execution host owns only code execution and host-local diagnostics:
| Host | Runtime | Execution location | Isolation guarantee |
|---|---|---|---|
| Renderer host | browser/PWA and fallback | active renderer | transactional lifecycle and structured unsupported-API errors, but no preemption |
| Electron sidecar host | Electron desktop | Electron main-owned JS child process pool | request timeouts, crash containment, sidecar restart, and capability-brokered APIs |
The renderer host and Electron sidecar host must expose the same lifecycle surface to PluginManager so boot remains discover -> preflight -> activate plugins -> restore layout.
Electron Sidecar Shape
Electron main owns the sidecar lifecycle. The renderer never receives Node.js primitives, child-process handles, raw file paths outside the selected vault, or the real Electron IPC surface.
The sidecar manager follows the proven notebook DuckDB sidecar pattern:
- fork a JS child process from Electron main
- use request/response messages with unique request IDs
- enforce a default 30-second request timeout
- reject all pending requests when the sidecar exits
- restart after crashes or timeouts only when there are still hosted plugins to service
- shut down during app quit and when the owning vault/window closes
The preload bridge exposes typed desktop_plugin_host_* IPC commands. The Electron host supports prepare, evaluate, activate, deactivate, and shutdown against Electron main-owned JS child processes, includes the initial parent-brokered capability request path, and advertises plugin-sidecar with status: "available". The current desktop hardening slice runs one trusted child process per plugin ID and vault/window context, so crash recovery and restart budgets are isolated per hosted plugin. The shared renderer still preserves the renderer host as the compatibility fallback and selects the sidecar only for desktop/sidecar-aware plugins or plugins that request brokered capabilities.
The Electron sidecar CommonJS v1 path is bundled-only. It evaluates the selected
entry file as a single CommonJS bundle; synchronous local require("./chunk"),
parent-relative, or absolute require() calls fail with a clear bundle-required
diagnostic instead of attempting async vault reads inside CommonJS resolution.
The sidecar hosted dependency surface is generated from the shared host-module
catalogue and is intentionally limited to lapis and @lapis-notes/api.
Renderer compatibility modules such as obsidian, Svelte, DOM and CodeMirror
view packages, and @lapis-notes/ui are unavailable in the sidecar. Dependency
failures include the host, selected module format, selected entry, and rejected
specifier so PluginManager can preserve actionable diagnostics in the
settings UI.
Capability Broker
Hosted community plugins must not receive the real renderer App graph. They receive a capability facade assembled by the selected execution host and brokered through PluginManager policy.
The initial background-safe capability set is:
| Capability | Purpose |
|---|---|
vault:read | Read vault-scoped text/binary files and metadata needed by the plugin |
vault:write | Write, create, rename, and remove vault-scoped files through existing path-safe adapters |
plugin:data | Load and save plugin-scoped data.json |
commands | Register and unregister serializable command contributions |
notices | Request renderer-owned notices without direct DOM access |
settings | Read app/plugin settings and register declarative plugin settings surfaces |
metadata:query | Query indexed metadata snapshots without receiving live renderer cache internals |
events | Subscribe to approved serializable app/vault/plugin lifecycle events |
logging | Emit structured plugin logs and diagnostics tied to plugin ID and host mode |
The first hosted phase does not include raw DOM access, direct Svelte component mounting, live CodeMirror instances, arbitrary renderer mutation, Node.js built-ins, network access, or direct filesystem paths outside vault-scoped broker calls.
Community manifest extensions may declare supportedRuntimes, requiredCapabilities, and executionHints, while baseline Obsidian manifests remain valid when those fields are absent. Runtime-aware preflight rejects a plugin before evaluation when declared requirements cannot be satisfied by the selected host. The renderer host currently grants no brokered hosted capabilities, so plugins that explicitly require brokered capabilities wait for a capable host instead of running against an unsafe partial facade.
IPC Protocol Direction
The Electron host protocol uses renderer-to-main IPC for lifecycle requests and main-to-sidecar child-process messages for execution. All payloads must be structured-clone safe.
Planned renderer-visible commands:
| Command | Purpose |
|---|---|
desktop_plugin_host_prepare | Create or reuse the sidecar context for a vault/window and return status |
desktop_plugin_host_evaluate | Evaluate a community plugin bundle after manifest preflight, including the selected runtime entry metadata |
desktop_plugin_host_activate | Run hosted plugin onload() against a capability facade |
desktop_plugin_host_deactivate | Run hosted plugin onunload() and dispose registered contributions |
desktop_plugin_host_shutdown | Stop the sidecar context for a vault/window |
Capability calls from the sidecar back into the renderer-visible app must go through main-owned broker requests, not ad hoc require("obsidian") access to renderer objects. Contributions that affect renderer UI, such as commands and future settings views, are returned as serializable descriptors and installed by PluginManager in the renderer.
Failure And Recovery
Sidecar failures must preserve the current plugin-runtime containment rules:
- a sidecar evaluation or activation error marks only that plugin as
failed - a sidecar crash marks hosted active plugins as failed, rolls back renderer-installed contributions, and keeps workspace boot moving
- timeout errors include the request type and plugin ID in diagnostics
- restart attempts are bounded and visible through plugin diagnostics
plugins-loadedstill fires only after all activation attempts settle
The community plugin settings UI surfaces host mode, requested capabilities, granted capabilities, requested and granted app permissions for indexed Lapis extensions, a clear trusted-desktop warning when that runtime is declared, last failure, and restart/disable actions for both registered plugins and preflight/evaluation failures that never produced a plugin instance.
Workspace Trust is the user-facing policy layer on top of this boundary.
App now owns a vault-scoped WorkspaceTrustService backed by the active
adapter vault id through a ScopedVaultStore("workspace-trust") record. Trust
defaults to trusted for existing vaults until the user explicitly revokes it,
and the service exposes the persisted trust state, grant(), revoke(),
request(), plus changed and requested events for future prompt flows.
When a workspace is untrusted, community plugins cannot activate trusted
desktop runtime entries, cannot receive brokered hosted capabilities, and
cannot register compatibility settings tabs. Plugin diagnostics preserve the
selected host mode, requested capabilities, and the trust-gated failure reason
so the settings UI can explain why the plugin stayed blocked before evaluation.
App also mirrors the current state into the workspace.trusted context key
so when clauses can react to trust state.
The workspace Community Plugins settings section now shows a top-level Workspace Trust row with current status plus trust and revoke actions. Trust does not make trusted desktop code safe by itself; it only gives the app a clear place to gate powerful plugin features and explain the decision to users.
Implementation Phases
- Done:
PluginManagercommunity-plugin evaluation is behind a runtime-neutral execution-host interface while preserving the renderer host as the default. - Done: Electron main/preload sidecar IPC skeleton and child-process manager exist for prepare/shutdown smoke coverage.
- Done: hosted-plugin capability facade types and the initial Electron broker path exist for vault I/O, plugin data, commands, notices, settings reads, metadata queries, approved event subscriptions, and logging.
- Done: runtime-aware manifest preflight and diagnostics UI exist for host mode, capability decisions, last failure, restart, and disable actions.
- Done: Electron sidecar evaluate/activate/deactivate runs trusted Lapis desktop entries through brokered capability facades while
PluginManagerremains the renderer lifecycle authority. - Expand renderer-safe declarative contribution models for richer community plugin UI.
Each phase must preserve the current plugin boot invariant: layout restoration begins only after community plugin activation attempts have completed.