NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · Installation, versioning and changelog
Developer reference

Installation, versioning and changelog

Installation from the licensed catalog

The marketplace catalog is an allowlist served by the licensing server. Installation accepts a plugin name present in the catalog, not an arbitrary URL or local folder. The administrator opens the plugin marketplace, reviews the manifest metadata and compatibility requirements, and starts installation.

The host fetches the catalog over HTTPS with the installation licence token (revalidated with an ETag, falling back to the last-good cache when offline). The locally evaluated licence plugin set filters what the catalog shows: a plugin outside the licence is hidden, and installing or updating it is refused the same way the server refuses it. Each payload is a tarball pinned by the catalog's sha256 and size, verified while streaming; the archive listing is validated before extraction (regular files and directories under the plugin name only, no symlinks or escapes), and the payload manifest name and version must match the catalog entry. Catalog update status compares the installed manifest version with the validated catalog version, even when an older payload remains cached. Listing downloads no payload; candidate preparation verifies its exact pin. After parsing, loader, marketplace staging and update preflight call validateManifestFolder(manifest, expectedName, directory) in src/plugins/manifest.ts. It requires the manifest name to equal the logical plugin folder name and returns the resolved entry path only when it stays lexically within the directory. The expected name is explicit because a staging directory can have a different basename. This check performs no filesystem I/O and does not parse the manifest again. A mismatch or escape throws before import or swap. Marketplace staging separately rejects symlinks and bounds file count and bytes; preflight separately resolves real paths and requires both plugin and declared service entries to be files inside the plugin tree. Preflight keeps the injected target-core parser and checks the effective enabled baseline plus every replacement.

The marketplace gates the downloaded candidate's consent and control dependencies before queuing the pinned version and sha256. It rejects built-in plugin names, unsafe names, malformed catalog entries, excessive plugin trees, a newer requiresCore or an incompatible requiresSharedApi. A missing required control returns the existing dependency error naming it and available providers. A consent refusal changes no executable files; confirmation replays the same install or update with the returned acknowledgeGrants.

POST /plugins/marketplace/:name/install with optional enable and acknowledgeGrants, POST /plugins/marketplace/:name/update with optional acknowledgeGrants, and POST /plugins/marketplace/:name/repair with an empty body return 202 { requestId, queued: true }. Install enables by default; enable:false still uses the same restart and proof transaction but needs no enablement consent. Any request also advances every other enabled plugin with a newer version in the same run, so a shared-API bump moves the whole stale set at once; disabled plugins advance when they are enabled. POST /plugins/marketplace/update with an optional acknowledgeGrants map keyed by plugin name queues the update of every stale plugin in one request and returns 202 { queued: true, plugins }.

Only the independent update coordinator stages and replaces publisher-plugin code. It shares the core transaction: preflight with the target parser, stop the daemon, web and affected independent plugin sockets/services, record observed backups and the enablement delta, swap, start, prove, then publish receipts or roll the whole set back. Disabled plugins are proven by tree inventory; enabled plugins and their independent service starts must converge. There is no per-plugin pending-install record or daemon-side settlement. Bundled removal persists disablement and soft-removal together with cancellation of queued plugin work inside one plugin mutation lock. User uninstall cancels queued work before awaiting gateway and service shutdown.

Installed publisher identity is the receipt plus an unchanged tree, even when the running manifest parser rejects it. Such a plugin remains Installed with its receipted version and degraded reason, and Repair submits an explicit retry. Boot detects missing or unloaded enabled receipted plugins after registry construction and queues repair without changing files or appending journal intents. It never repairs customer plugins or queues during an active non-terminal journal. A version that failed activation is not repaired automatically again; offline, licence and consent refusals do not consume that activation guard.

Admin GET /system/update is the status authority for the queued request: { queue, journal, report }. The journal field is null once the journal reaches a terminal state. Match the request id against journal operations or the report's requests and per-plugin requestId; acceptance alone is not proof. Each per-plugin outcome is one of applied, rolledBack (with a detail), needs-consent, not-licensed, superseded, failed or skipped. The report state needsOperator marks a result that needs operator attention. The coordinator rechecks consent and renewed entitlement; a catalog pin that moved is superseded rather than replaced with different bytes.

Automatic selection plans against the core the transaction installs, or the running source core for plugin-only runs. Catalog version pins the offered release and must match the payload manifest; the description is publisher display metadata. A candidate whose requiresCore exceeds the effective target is deferred as needs-newer-core. Unchanged baseline plugins, including customer plugins, must parse on the target before services stop.

Versioning

Keep these axes separate:

  • The daemon version is the core package version. It is the version compared by requiresCore for a manual install; an automatic update compares the core its transaction installs instead.
  • The plugin's version is its own release version. Bump it whenever installed bytes change.
  • apiVersion is the exact breaking plugin API contract.
  • requiresCore is a minimum core version checked by registry installation.
  • requiresSharedApi is an exact shared-helper contract checked before the entry is imported and during installation.
  • web.requiresApiVersion and the browser registration's requiresApiVersion target the browser runtime contract, currently 54 (PLUGIN_UI_API_VERSION in packages/plugin-ui-kit/index.js).

A new hook like brain.run.beforeSettle is additive: plugins that use it raise requiresCore, not apiVersion (stays "2") and not the browser API (the PLUGIN_UI_API_VERSION axis, unrelated).

Do not use plugin version fields to communicate API compatibility. Update the plugin manifest and registry catalog together.

An artifact patch that only raises the plugin-API floor does not need an in-app note; a release ships one only when something changed for the people using Elowen.

Writing a changelog entry

The in-app What's new page (/p/changelog) is for people using Elowen, not for developers. Only a release with a big UI change or a new feature, something a user or an administrator can be shown in the app, gets a short, dated update; most releases get none. Bug fixes, polish, relabels, speed-ups and technical detail belong in CHANGELOG.md and the developer docs, never here.

While a release is being developed, write its note as plugins/changelog/entries/unreleased.md (English) with unreleased.cs.md and unreleased.sk.md beside it, and commit all three. The page never shows them. npm run release:prepare produces one entry per calendar day, in <version>.md, <version>.cs.md and <version>.sk.md. If that day already has a patch entry, it appends the new content in every language, keeps the day's title and advances its file name and version to the new patch. This makes the appended content unread even for readers of the earlier patch. The product-line overview is separate. It uses the plugin's front-matter parser and includes all consumed and renamed paths in the release commit. A release without an unreleased note ships no update; a note missing a translation, or one that was never committed, stops the release before anything changes.

---
title: The agent sees what each click did
---

One or two sentences: what this update means for the reader.

### New

- **Short name.** One sentence: what you can do now. Where: [Settings → Models → Model roles](/settings?cat=models)

### Improved

- **Short name.** What works better, faster or more simply, and where.

### Changed

- **Short name.** What behaves differently, or what the reader has to do.

:::admin
- Only administrators receive this part: installation, updates, licences,
  plugin compatibility, migrations and backups.
:::

Plugin-specific sections or bullet groups use the same container syntax as administrator notes:

:::plugin sandbox
### Stop a desktop

Stop a desktop from Account while keeping the Project running.
:::

:::admin
:::plugin discord telegram whatsapp
### Voice messages

Configure voice for your connected chat apps.
:::
:::

Names are manifest plugin ids, separated by spaces. Multiple names mean any of those plugins makes the item applicable; the page shows pills only for plugins this account can use. Admin and plugin containers may nest, but plugin containers may not nest inside each other. Use a heading or a bold bullet title inside each plugin container; the page places a small display-name and optional icon pill next to that title, using the shared Badge. Keep the same scopes and audience in cs, sk and en. Untagged core content remains public. The daemon checks the live core access predicate, including installed/loaded, enabled and per-account grants, for admins too. Filtered items never leave the daemon. A note with no remaining content disappears from its listing and unread badge; headings alone are not content. Unclosed containers retain restrictions to the end and log a warning; malformed or stray container markers fail loading.

Every section is optional; keep this order. Name places in the app with the labels the web dictionaries and plugin strings use, as an in-app link when the place has a URL (/settings?cat=models, /p/editor) and as a bold click path otherwise. Do not list plugin versions: write a plugin's change as an ordinary item in the matching section. Translations are natural Czech and Slovak with formal address, not word-by-word copies.

The page shows each update by its date and title. Administrators see the release version as a tooltip on the date; nobody else sees version numbers. The :::admin container is cut out on the daemon: a non-administrator is never sent its text, and an update whose only content is administrator notes is not listed or counted as unread for them. A container left open runs to the end of the note, so a missing ::: hides text rather than showing it to everybody. A link to a path inside the app (starting with /) opens through the app's own navigation; other links behave as ordinary links.

The parser is intentionally a small flat front-matter parser, not a general YAML parser:

  • It strips an optional BOM and recognizes a front-matter block only when it starts with ---, has flat key: value lines, and ends with ---.
  • Values in brackets become comma-separated arrays with surrounding quotes removed. The literals true and false become booleans. Other values are strings with one pair of surrounding quotes removed.
  • version is required and must be non-empty. Entries without it are skipped with a warning, except the unreleased files, which are skipped silently. Duplicate English versions are skipped.
  • date, title, tags, and pinned are optional. The parser does not validate the date format. tags is an array only when the value uses bracket syntax. pinned is true only for the literal true.
  • The English file owns date, tags, and pinned. A recognized translation contributes only its title, body, administrator section and plugin-scoped items. A translation without an English original is skipped, as is a duplicate translation of the same language and version. Missing translations fall back to English.
  • Only cs and sk filename suffixes are translations. Other Markdown files are treated as English candidates.
  • Entries sort with pinned entries first, then by descending numeric version. Every segment counts and a missing one reads as zero, so the line overview 0.29 sorts below the update 0.29.54. Text after the first hyphen is ignored, and a segment that does not start with digits sorts as zero.
  • The body is trimmed Markdown. Unknown front-matter keys are ignored.
  • Release assets belong under entries/assets/<version>/, which the plugin serves at /plugins/changelog/api/asset/<version>/<file> for authenticated users. The asset route accepts only png, jpg, jpeg, gif, and webp, and rejects SVG. Explicit assets/<version>/file.png references keep that asset version when daily release preparation advances the entry's identity.
  • The page uses the entry version to calculate each account's unread state. Opening the page records the newest visible update, so every later update is new to that account. An admin can reset an update to unread for all accounts.

Owning code and limits

  • Parser and visibility rules: plugins/changelog/lib/entries.mjs. Routes, asset serving and the per-account marker: plugins/changelog/lib/api.mjs. Release preparation: scripts/release-prepare.mjs, which runs the same parser for the day's merge.
  • Minimal use: write unreleased.md with its cs and sk translations and commit all three. The release procedure then turns them into the day's entry; no other plugin code is involved.
  • Absence: a release without a note ships no update, so the page lists no entry for that release.
  • Limits: the parser is a flat front-matter reader, not YAML. Container markers must be exact, and a malformed one stops loading. Assets are limited to the five raster types above.
  • Cost: entries are parsed once per boot, so a note change needs a daemon restart, and the page reads the account's marker only.