NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · Controls and plugin dependencies
Developer reference

Controls and plugin dependencies

A plugin resolving ctx.control(key) must declare reads: ['controls'] and include that key in consumesControls. An absent or missing key is refused with a local diagnostic and returns undefined. Privileged keys independently enforce the host's authority allowlist; declaration alone never grants credentials, process launch or artifact access. Resolve the control on each use. A disabled, missing or incomplete owner returns undefined. Sandbox artifacts remain core-only. The Files plugin declares sandbox and skillResources as a real consumer.

Controls and plugin dependencies

A control is a live domain interface, not a plugin-name lookup. Publish a key with ctx.registerControl(name, control) and declare it in provides.controls. Consume a sibling with ctx.control(name) and declare reads: ["controls"]. Resolution happens at call time and returns undefined when the provider is disabled or unavailable. Treat that as a real state and fail honestly. Never cache the returned object.

Owning code: src/plugins/registry.ts (control, registerControl) and src/plugins/api.ts (PluginContext). A consumer also needs its key in consumesControls when the manifest has that field. Without it, or without reads: ["controls"], ctx.control() returns undefined and logs a warning (src/plugins/registry.ts, control). A provider whose registration lacks a method that core calls for that key counts as absent (KNOWN_CONTROL_METHODS in src/plugins/registry.ts).

The bundled Files plugin declares skillResources in consumesControls for managed Read support files. Core provides that authority from the live, grant-, owner- and override-filtered skill catalog. Only Files may consume it, and only canonical directory-form skill roots authorize a read; flat skills do not grant access to their shared loader folder. A hidden skill or missing provider grants no host resource access. Real consumer: plugins/files/index.mjs calls ctx.control('skillResources'); the provider is registered in src/daemon/brainCore.ts.

requiresControls names control contracts needed before enablement. The host uses the manifest declarations to report missing providers without coupling one plugin to another plugin name. Declare a key here when the plugin cannot do its job without its provider; a conditional feature, such as managed-project access in a plugin that also serves host projects, stays in consumesControls only. Core-owned keys, such as skillCatalog or machineRuntimeGateway, are not plugin dependencies. A registration may also set registerControl(name, control, { requires }) for a live control dependency.

Enablement enforces the declaration. When no enabled provider publishes a required key, the enable request answers 409 with error: "missing plugin dependency" and controls: [{ key, providedBy }] (src/api/routes/plugins/index.ts, enablePlugin; missingControls in src/api/routes/plugins/marketplace.ts). An unknown key has no provider and therefore blocks enablement.

The licence server derives optional catalog requiresControls: string[], provides.controls: string[] and label: string from the manifest archived for that exact version, alongside its compatibility requirements. Never hand-write these dependency fields into the registry. Omitted fields make no claim; older core catalog parsers tolerate unknown fields. Core reads these fields in src/plugins/marketplace.ts. The derivation runs on the licence server, which is not part of this repository.

Admin GET /plugins and GET /plugins/marketplace add dependencies: one row per resolved provider plugin, shaped as { name, label?, i18n?, enabled }, where i18n carries installed manifest locale labels. Installed manifests override catalog declarations for the same provider. Catalog-only, disabled and soft-removed providers have enabled: false. Multiple controls provided by the same plugin yield one row; self-provided controls yield no pill. Unknown keys still refuse enablement, but produce no invented plugin identity. Unreadable installed manifests have no dependency claims to display.

The same resolveControlDependencies helper matches control keys for the enable gate and both listings. A key is satisfied when any installed enabled provider publishes it; enabling a candidate also counts its own controls. The gate keeps its existing 409 controls: [{ key, providedBy }] response. Listing resolves against the licence-filtered catalog and installed manifests, with one catalog refresh/cache read and one API-side plugin scan per request and no per-dependency network work. An unavailable catalog retains known installed providers. The helper lives in src/plugins/controlDependencies.ts; the listing code is in src/api/routes/plugins/index.ts and src/api/routes/plugins/marketplace.ts.

Settings/Plugins shows the resulting providers beside each card's version, including Available and soft-removed bundled cards. OneDrive's microsoftIdentity requirement resolves to Microsoft Teams. Outline Badge tones are muted for enabled providers and warning for unavailable ones; shared HelpTip explains the required display names on hover, focus or tap. No requirements or no known provider means no pill. The pills render in DependencyPills in web/modules/settings/PluginsSection.tsx. Both OneDrive (requiresControls) and Microsoft Teams (provides.controls) are plugins in the plugin registry, not in this repository.

The microsoftIdentity control gives OneDrive a fresh drive-scoped Graph client through driveGraphFor(accountId). An absent binding or typed missing sign-in returns null without a warning; verification and transport failures reject. Consumers keep their current link state on an outage rather than requesting another sign-in. No provider means no client or request; the existing delegated-session verification owns tokens and network cost, and the consumer owns failure reporting.

The bundled elowen-docs plugin publishes docs: search(query, limit) returns the best sections of the shipped manual and plugin docs as { path, title, heading, text } (heading is the place within the page, without repeating its title), ranked exactly like DocsSearch (semantic with an embedding model, keyword overlap without). Core reads it in POST /search/ask, where the command palette's Ask AI grounds a short answer in the top eight sections; the lookup gets five seconds of the ask's fifteen. With the plugin disabled, failing or slow, the ask still picks pages and simply shows no answer text. A plugin may read it too by declaring it in consumesControls. It costs one query embedding per call once the index exists; the first call after an upgrade builds the index. Code: plugins/elowen-docs/index.mjs (registerControl('docs')); consumer lookupDocs in src/api/routes/search.ts; the constants SEARCH_ASK_PASSAGES (8), SEARCH_ASK_DOCS_TIMEOUT_MS (5000) and SEARCH_ASK_TIMEOUT_MS (15000) are in src/search/siteSearchAsk.ts.

Controls currently used by the built-in plugin set include delegation, terminal, cron, workflow, MCP, LSP, code mode, sandbox, identity, GitHub, site gateway, browser capture, the skill catalog, and documentation search. Use domain keys, not current plugin names.

The table maps those domains to their keys and to where each provider is registered. Some providers are plugin-registry plugins, not bundled in core, and are marked as such.

DomainKeyProviderRegistered in
Delegationsubagent, workflowBundled subagent pluginplugins/subagent/index.mjs, plugins/subagent/lib/workflow.mjs
TerminalterminalBundled terminal pluginplugins/terminal/index.mjs
Code modecodeModeBundled code-mode pluginplugins/code-mode/src/index.ts
Sandboxsandbox, sandboxArtifactsBundled sandbox pluginplugins/sandbox/index.mjs
Documentation searchdocsBundled elowen-docs pluginplugins/elowen-docs/index.mjs
MCPmcpBundled MCP pluginplugins/mcp/index.mjs
CroncronPlugin registry (cronjob)plugins/cronjob/lib/filing.mjs in the registry
Browser capturebrowserCapturePlugin registry (browser)plugins/browser/src/index.ts in the registry
GitHubgithubPlugin registry (github)plugins/github/src/index.ts in the registry
IdentitymicrosoftIdentityPlugin registry (Microsoft Teams)Microsoft Teams plugin in the registry
Site gatewaypublishedSitesGatewayCore host, when configuredsrc/daemon/brainCore.ts
Skill catalogskillCatalog, skillManagement, skillResourcesCore hostsrc/daemon/brainCore.ts
Machine runtimemachineRuntimeGatewayCore hostsrc/daemon/brainCore.ts

Core host keys are not plugin dependencies, so they never produce a pill.