NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · Browser and plugin ownership
Developer reference

Browser and plugin ownership

Owning code:

  • src/plugins/loader.ts, src/plugins/registry.ts and src/plugins/pluginsProvider.ts: the plugin loader and the current registry. PluginRegistry.contextFor is at src/plugins/registry.ts:1362 and PluginContextOptions at line 257.
  • src/plugins/capabilities.ts:15: the public PluginCapabilities type, inferred from the manifest schema.
  • src/plugins/contributionReport.ts:47: buildContributionReport.
  • src/plugins/manifest.ts:37: PLUGIN_API_VERSION, currently '2'.
  • src/brain/toolOutput.ts:26: makeToolOutputPolicy. src/brain/messageView.ts:351: toolOutputView.
  • packages/plugin-shared/duration.mjs:2 and web/lib/format.ts:26: the duration formatter and its web mirror, pinned by tests/contract/webMirrorContracts.test.ts.
  • packages/plugin-shared/projectChoice.mjs:9, packages/plugin-shared/projectExecution.mjs, packages/plugin-shared/subagentName.mjs (five words and 40 characters, lines 5-8) and packages/plugin-shared/toolLists.mjs.
  • packages/plugin-shared/index.mjs:20: PLUGIN_SHARED_API_VERSION, currently 8.
  • packages/plugin-ui-kit/index.d.ts: the plugin UI kit types. useNow is at line 1198.
  • web/app/layout.tsx:155: the fixed dark data-theme.
  • web/lib/queries.ts:206 (useSystemUpdateStatus) and web/lib/operationDockUpdates.ts:20 (updateDockOperations).

Browser application and packages

  • web/app/ contains Next.js routes and page composition.
  • web/components/ contains the shell, navigation, command palette, UI primitives, and shared browser components.
  • web/modules/ contains feature surfaces such as advisor chat, dashboard, settings, Projects, and account pages.
  • web/lib/ contains browser-side API clients, data contracts, event handling, search, presentation helpers, and mirrors of selected shared contracts. The root layout owns the app's fixed dark data-theme; terminal auto mode separately uses the dark terminal palette, while custom terminal palettes are per-account settings.
  • web/tests/ and the web package scripts provide browser unit and end-to-end coverage.
  • plugins/*/web-src/ is the source location for plugin browser bundles when a plugin contributes browser UI.
  • packages/plugin-shared/ contains shared plugin contracts and utilities. The core tarball includes it as a local file: dependency, which npm installs into the core's node_modules without a registry lookup. Installed plugins link to that host node_modules and resolve the same shared contract; when a plugin does not import it, nothing is loaded. Do not bundle the core's node_modules: npm can prune unrelated transitive dependencies during a global install. For example, the Telegram plugin imports elowen-plugin-shared/format through the host link. The plugin registry's Discord, WhatsApp, Telegram, MS Teams and Cronjob plugins import it the same way, for example plugins/telegram/lib/format.mjs:6. The core package declares the file:packages/plugin-shared dependency in package.json:147. The pure elowen-plugin-shared/duration entry formats milliseconds as floored 8s, 12m 14s, or 1h 2m; the CLI and platform cards import this one implementation, and plugin bundles reach it through the web runtime utility utils.formatDuration. The web app keeps a byte-identical mirror in web/lib/format.ts, because Turbopack resolves modules only inside the web root; tests/contract/webMirrorContracts.test.ts pins the two. A caller that does not display a duration imports nothing; compact single-unit relative timestamps remain a separate format. Likewise elowen-plugin-shared/projectChoice (resolveProjectArgument) is the one reading of a typed /project <slug|id> argument, imported by the adapters' shared picker core and by the CLI. The verified importers are packages/plugin-shared/chatCommands.mjs:35 and src/cli/chat/picker.ts:8.
  • packages/plugin-shared/projectExecution.mjs owns reserved guest roots, slug normalization, managed guest mount paths and the artifact root. Core retains the typed Project reference parser/facade; Sandbox imports the same runtime owner for mount validation and environment paths. Historical empty/reserved slugs keep their stable Project-id path, and container hash preimages stay unchanged. These pure helpers grant no authority and perform no I/O. The staged subagentName.mjs owner preserves the five-word/40-character delegated-child labels for the coordinated store/migration/Subagent cohort without rewriting names. Shared ask, display and modelIdentity own transport-neutral adapter parsing, vocabulary and catalog decisions; imageRuntime calls the existing image and authorized Project file seams once per render. Package imports register nothing; detailed use, absent behavior, bounds and costs live in the shared README and PLUGIN_DEV shared runtime helpers.
  • packages/plugin-ui-kit/ contains the reusable plugin UI kit used by the web application and plugin bundles. Its staged hooks.useNow signature publishes the existing host clock directly; enabled host and plugin consumers share one visible-tab heartbeat, documented in WEB Shared clock.

The existing language producer check parses EnvironmentAction discriminants and Sandbox STEP_PLANS tuples with the TypeScript parser. Comments and unrelated string properties cannot keep an orphan translation alive; unresolved variants or dynamic plan entries fail as incomplete coverage. Action labels are checked both ways, while historical step labels remain supported. This is bounded producer coverage, not general translation liveness.

Shared helper subpaths are shipped inside the core artifact together with their adjacent .d.mts declarations. Import each helper from its explicit elowen-plugin-shared subpath; the package root does not re-export helper bodies. NodeNext resolves the adjacent declaration for the .mjs export. Each declaration has an exact analysis entry. These additions retain shared API 8, but consumers must require the first verified core release containing the helper and its export metadata. API 8 alone does not establish availability. Unused helpers perform no work; a missing target or unsupported subpath fails module loading. Validate the packed artifact as well as the source tree.

/xml exposes sanitizeXmlText, xmlEscapeText and xmlEscape. Import xmlEscape from elowen-plugin-shared/xml for attribute values, or xmlEscapeText for text nodes. The canonical rules replace XML 1.0-forbidden C0 controls, escape ampersands and angle brackets, and additionally escape both quotes for attributes. The helper converts values with String before escaping. Core src/shared/xml.ts delegates to this module; Subagent delegation and the migrated Todo and Chatbot contexts are real consumers. No invocation means no transformation. These pure string operations scale with input size and do not parse or validate complete XML documents.

/configTokenList exports parseConfigTokenList(value) for messaging configuration. Arrays are already tokenized: stringify and trim elements, then remove empty entries while preserving embedded commas and newlines. String values split on commas and newlines. Missing values produce an empty list; order and duplicates remain intact. Discord threadIds, Telegram allowedChatIds and WhatsApp groupIds use it when reading adapter configuration. For example, parseConfigTokenList(['123', '456,789']) preserves two tokens. Work scales with input size. It neither validates configuration writes nor parses Codebase globs.

/operationInitiator exports operationInitiatorFromCredentialScope(scope). Call it with the host-verified req.auth.credentialScope at authenticated HTTP operation creation. full and impersonation map to ui, agent maps to agent, and api, advisor or an absent origin map to system. Sandbox environment operations, core Project routes and Cronjob manual runs share this classifier. It performs constant-time classification without authorization or persistence. Request bodies and account roles do not determine origin; non-HTTP callers retain their existing defaults.

/withTimeout exports withTimeout(work, ms, failure), where work is a Promise or a callback returning one and failure is a string or Error. For example, await withTimeout(() => cdp.send('Tracing.end'), 1000, new BrowserDeadlineError('Trace end timed out')). Callback work starts once after the timer is armed. Work results and failures, including synchronous throws, preserve identity. Expiry creates an Error from a string or rejects with the supplied Error unchanged. The timer is unref'd and cleared on settlement. This helper never cancels work: callers own late effects and cancellation. Each call uses one timer with Node event-loop timing; without a call it schedules nothing. Browser tracing after migration and core platform startup through src/shared/withTimeout.ts are real consumers. Resolve-on-expiry waits keep their own semantics.

Files uses shared imageSniff for bounded signature classification and retains full-byte PNG/APNG, JPEG-LS and BMP eligibility checks. Ordinary image Read and rendered PDF pages call prepareInlineImage(raw, mime, policy) from elowen-plugin-shared/inlineImage during result construction. Both keep a 3,750,000-byte raw fallback cap. Read does not fall back after unsupported resized MIME; PDF does. Omitted images do not grant mutation authorization. MCP result mapping calls the same helper with maxRawBytes: Infinity, fallbackOnUnsupportedResize: true and original rawData, preserving its existing fallback bytes. Successful resizing remains bounded to 2000 pixels per edge. Notebook outputs iterate the shared PNG, JPEG, GIF, WebP set with local byte validation and limits.

Files seedReadStateFromHistory(sessionId, messages) restores authorization atomically from successful visible Read results carrying content hashes and returns no count. Without a session it does nothing; invalid or failed results grant nothing. Host and managed-project keys stay separate, and replay replaces prior session state. The Files brain.session.afterSpawn hook is its production consumer. Session and file bounds remain 64 and 512.

MCP serverPresentation owns reconnect classification and summarizeReconnectResults(settledResults). CLI and browser reconnect-all share connected, project-verified, sign-in-required and failed counts. Rejected requests count as failed; denied authorization retains explicit auth labels. Empty input yields zero counts. This helper performs no requests and changes no scheduling: CLI remains concurrent, browser sequential. reconnectFailure uses the same classifier.

The Subagent plugin owns AGENT_NAME_RE in lib/agentTypes.mjs. Agent-file parsing and lib/agentCatalog.mjs save/delete validation share it: one to 64 lowercase alphanumeric characters with single interior dashes, no trailing or repeated dash. Invalid definitions are not loaded and invalid writes are rejected. Core plugin names deliberately use a different grammar requiring two characters and permitting repeated or trailing dashes. Sharing this constant adds no filesystem operation.

The terminal cleanup deadline remains private. Existing cleanup helpers use it by default and remain internal regression-test entry points; the deadline is not an exported consumer option. Cleanup still reports an overdue wait once and waits for confirmed process exit.

Plugins

The repository's plugins/ directory contains bundled plugins and their manifests. A plugin directory can include an entry module, manifest, plugin-owned library code, database migrations, browser source, and assets. The runtime also discovers enabled installed plugins from its configured plugin directories. The manifest declares the plugin's contributions and dependencies.

The core loads plugins through src/plugins/loader.ts and exposes the current registry through src/plugins/registry.ts and src/plugins/pluginsProvider.ts. Plugin controls are typed and resolved at call time, so a consumer does not retain a control from the generation that published it. Subagent owns its built-in definitions, editor, and one immutable per-generation type catalog; core grants only its fixed user-definition directory and validates model/tool names through generic host seams. A new typed spawn without the live Subagent control is refused. The host still enforces the read-only tool policy. Cronjob owns browser job requests and schedule validation through its server preview; the browser runtime offers only generic query infrastructure and conversation-link invalidation. API 26 retires the former cron-specific runtime hooks and parser. The API numbers in this paragraph follow the plugin UI kit series annotated in packages/plugin-ui-kit/index.d.ts, where API 26 is the conversation-link invalidation hook (line 1206). They are separate from the manifest contract version PLUGIN_API_VERSION (src/plugins/manifest.ts:37).

Internally, PluginRegistry.contextFor(name, options: PluginContextOptions) constructs a staged context with named loader wiring, for example contextFor('files', { config: {}, logger, capabilities }). Configuration and logger are required; optional callbacks preserve the existing absent-host refusal or empty projection for their respective public accessor. The loader's tool-name, command and sibling-control callbacks read the merged registry at call time, so staging order and live configuration remain authoritative. The public PluginCapabilities type comes from src/plugins/capabilities.ts, inferred from the same schema the manifest validates, rather than repeating its vocabulary. Context-local capability checks preserve synchronous throws, rejected promises, warning text and reporting metadata. Construction adds no I/O beyond the existing context preparation.

buildContributionReport(registry, owner?) is the single runtime contribution projection. Without an owner it reports the full registry; with one it selects raw ownership before projection, preserves registration order and duplicate hook names, and returns empty lists for a plugin with no contributions. The plugin detail API uses this same projection. Its work is linear in the registered contributions and performs no I/O. Manifest tool declarations and makeToolOutputPolicy reuse packages/plugin-shared/toolLists.mjs for exact names and trailing-star prefixes. Successful built-in output stays hidden by default; only plugin showOutput declarations supply patterns, while failure and hook-note visibility remains in toolOutputView.