NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · Plugin decisions and verification
Developer reference

Plugin decisions and verification

Decision table

I want to...Use this seam
Add model-callable domain behaviorctx.registerTool
Add progressive-disclosure instructionsctx.registerSkill
Add a slash macroctx.registerCommand({ kind: "prompt" })
Open a local browser chooserweb.chatPickers and a picker command
Add stable agent instructionsctx.registerSystemPromptFragment
Add data that changes on every turnctx.registerTurnContext(..., { placement: "after-user" })
Transform only explicit current-turn inputctx.registerInputTransform
Add a reminder during a long tool loopctx.registerStepContext
Learn skills from a finished turn in the backgroundctx.registerSkillPostTurnReviewer
Append a note or ask for one more agent run when a reply endsctx.registerHook with brain.run.beforeSettle
Observe or narrowly patch a wired lifecycle pointctx.registerHook
Expose a user-facing authenticated endpointctx.registerApiRoute
Receive an external callbackctx.registerHttpRoute
Rate-limit or audit a public plugin webhook by canonical network originPluginHttpRequest.origin; authenticate the webhook separately
Start an account-scoped synthetic platform turn and observe live progressPlatformControlApi.relay(src, text, observer)
Stream bytes to a browser or integrationctx.registerWebSocketRoute plus issueWebSocketTicket
Share a domain capability with another pluginregisterControl, control, and manifest consumesControls
Add a Project row indicatorregisterProjectIndicators or web.projectRows
Add fields to Brain statusregisterBrainStatusProvider
Add cleanup or periodic workregisterBootReconcile, registerService, or registerInterval
Render a plugin card, rail section, metric, or history branchweb.chatCards, web.chatRailSections, web.dashboardMetrics, or web.historyBranches
Keep something of the conversation's Project visible in its chatweb.chatDock
Persist domain datactx.db().migrate and ctx.dataDir()
Use a core security boundaryThe matching ctx.host accessor, never a copied ACL/path/process implementation

If the value changes every turn, it belongs in turn context with placement: "after-user". It never belongs in the system prompt, because the system prompt is the cached prefix. Every provider request still pays for the volatile text as input; system-prompt bytes are paid again only after a cold or invalidated prefix. Host-only details such as toolTrace and ownRow do not cost model tokens.

Where the seams are implemented

  • The PluginContext interface, with the signatures and rules of every register* method, is in src/plugins/api.ts. Platform relays use the PlatformControlApi interface in the same file (line 1273).
  • The implementations live in src/plugins/registry.ts, in the object that builds each plugin's context (about lines 1450-2366). Tools, platforms and HTTP, API and WebSocket routes are checked against the manifest first: an undeclared one is refused with a warning. Commands are checked for their name, collisions and prompt content instead.
  • Manifest validation for web slots, such as chatDock and chatCards, is in src/plugins/manifest.ts; the contributions reach the browser through src/plugins/pluginWebUi.ts.
  • The rows of the seam catalog are in the "Complete seam catalog" section of docs/PLUGIN_DEV.md. tests/contract/pluginSeamCatalog.test.ts fails when a new register* method or web slot has no catalog row.

Local development and verification

For a bundled plugin, install dependencies in the Elowen checkout and run the normal plugin build:

npm ci
npm run build:plugins-web
npm run build

The web build is needed when a plugin has browser sources. The main build compiles the daemon, builds browser bundles, and copies bundled plugins into the distribution tree. npm run build runs prebuild (language checks, then it cleans dist/), build:ts, build:plugins-web, build:desktop-agent, and build:assets (which copies plugins/ into dist/), and then postbuild, which fails when a plugin folder has no manifest or an expected file is missing (package.json, lines 68-74). A plugin developed outside the catalog builds and tests against a local core checkout: depend on the host packages with file: dependencies (elowen, elowen-plugin-shared, elowen-plugin-ui-kit) instead of the published versions, then run the host contract checks.

Host-lent SDKs

A registry plugin does not need to vendor an SDK the host already ships. After every install and update, the marketplace installer symlinks the plugin's own node_modules to the host's (linkHostModules in src/plugins/marketplace.ts), so the plugin's bare imports resolve against the host's copy at runtime. That is why grammy (Telegram), baileys and qrcode (WhatsApp), botframework-connector (Teams), and puppeteer-core and proxy-chain (Browser) have zero importers anywhere in this checkout: the host carries the dependency so it can be lent, not because it is dead. They are excluded from the dead-dependency sweep for the same reason, in knip.json's ignoreDependencies; do not remove one from package.json without first checking whether an installed registry plugin still imports it.

node-html-parser (Parts: the CoraHB e-shop and PartsLink24 HTML parsing) is lent the same way, but the bundled Sandbox guest also imports it, so it is not in ignoreDependencies. It must stay in dependencies, not devDependencies, or a production install does not carry it and the plugin cannot import it.

Use focused checks first:

npx vitest run tests/plugins
npx vitest run tests/api/pluginUiRoutes.test.ts
npx vitest run tests/plugins/marketplace.test.ts
npx vitest run tests/contract/pluginApiSubpath.test.ts
npx vitest run tests/contract/pluginSharedPackage.test.ts
npx vitest run tests/contract/pluginBundleContracts.test.ts
npx vitest run tests/contract/pluginSeamCatalog.test.ts
npm run check
npm test

For a manifest change, verify that the manifest parses, every declared entry exists, every declared browser entry and stylesheet exists, and the runtime contribution report matches the intended registration. For a route or grant change, run the focused API and plugin-grant tests. For a hook change, run the hook integration tests. Inspect the plugin log after a restart when a plugin is skipped.

Before publishing, test the exact built tree, not only source files. Do not commit generated output unless the owning repository requires it.