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, name, dir) 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
requiresCorefor a manual install; an automatic update compares the core its transaction installs instead. - The plugin's
versionis its own release version. Bump it whenever installed bytes change. apiVersionis the exact breaking plugin API contract.requiresCoreis a minimum core version checked by registry installation.requiresSharedApiis an exact shared-helper contract checked before the entry is imported and during installation.web.requiresApiVersionand the browser registration'srequiresApiVersiontarget the browser runtime contract, currently 66 (PLUGIN_UI_API_VERSIONinpackages/plugin-ui-kit/index.js).
A new observational hook 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.
Shared API 14 declared API patterns
Shared API 14 first shipped with package 0.11.0 and core 0.30.4.
Package 0.12.0 retains that exact API number while adding exported retry and
retry-presentation helpers, localized shared messages, typed host-condition errors and
local-execution classifiers. UI kit 0.24.0 publishes
UI API 67 and depends on shared 0.12.0. An API 14 match does not guarantee these
new subpaths on an older package; build and validate against the matching core.
The core release command assigns the artifact patch; package metadata is not proof
that a matching core artifact has been built or released.
Namespaced and root-mounted authenticated routes match only their complete declared
patterns. Replace remainder parsing with declared :name segments and read
req.params, which the host decodes once. List and item routes need separate
declarations and registrations. The catalog reports real patterns without a prefix marker.
Public /hooks routes retain their existing prefix behavior.
Every bundled, registry and external plugin must pin requiresSharedApi: 14.
The exact gate refuses API 13 before entry import, even for plugins without routes.
Patch each registry artifact and update its manifest, catalog, README and test pins.
Update core and the loaded plugin set together through the existing coordinator;
there is no compatibility routing path.
Earlier shared-API releases
The shared-API number advanced release by release from 10 (shared package 0.7.0) to 14 (0.11.0);
0.12.0 keeps 14 and only adds helpers. Each step was a coordinated release in which bundled, registry
and external plugins moved their pin in the same update, and the exact gate refused the previous number
before the entry was imported, whatever the artifact's requiresCore floor said.
The contracts those levels introduced are part of API 14 and are documented where they are owned: the
awaited database seam, schema-qualified migrations, transaction-local handles and the draining helpers in
Persistence and caches, and the host stores, project files, ctx.timezone() and the
image runtime in PluginContext and workflow results.
An installed plugin pinned below 14 is refused until it is rebuilt against the matching shared package
and installed with its paired core in one coordinator transaction; the loader carries no aliases or
adapters for older numbers. Change the artifact patch of every affected plugin and keep its manifest,
registry catalog and README metadata identical. A registry check uses the selected candidate checkout's
shared package and SDK; when that checkout has no current built SDK the gate stays blocked, because
another checkout's dist cannot validate the candidate.
Shared API 13 coordinated PostgreSQL release
API 13 introduced the awaited database seam, schema-qualified migrations and transaction-local handles. These contracts remain part of API 14; their current owners are linked above.
Shared API 12 history
API 12 made StateStore writes asynchronous. Callers await committed writes; this contract remains part of API 14.
Shared API 11 coordinated release
API 11 moved binary Project transfers to the host Project-files seam and image runtime. The current contracts remain in API 14.
Shared API 10 coordinated release
API 10 removed earlier configuration-migration, embedding and checkpoint contribution paths. Current plugins use their owning host seams and the API 14 gate, not those retired paths.
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 a separate entry of its own (0.29.md), read by the same
parser. The release commit names the note files it consumed and the entries it renamed. 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, carrying the plugin's display name and no icon. 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, because the page builds the pill row from that title with the shared Badge; an item without one fails to render. 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 flatkey: valuelines, and ends with---. - Values in brackets become comma-separated arrays with surrounding quotes
removed. The literals
trueandfalsebecome booleans. Other values are strings with one pair of surrounding quotes removed. versionis required and must be non-empty. Entries without it are skipped with a warning, except theunreleasedfiles, which are skipped silently. Duplicate English versions are skipped.date,title,tags, andpinnedare optional. The parser does not validate the date format.tagsis an array only when the value uses bracket syntax.pinnedis true only for the literaltrue.- The English file owns
date,tags, andpinned. A recognized translation contributes only itstitle, 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
csandskfilename 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.29sorts below the update0.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 onlypng,jpg,jpeg,gif, andwebp, and rejects SVG. An image written as a relativeassets/<version>/<file>keeps that folder in its URL, so an append that advances the entry's version still finds the images its author committed; any other relative source resolves under the entry's own version. The route answersimmutablefor a year, soGET entries/<version>also returnsassets, a content fingerprint per shipped image keyed<version>/<file>, and the page adds it to the image URL as?v=<fingerprint>: an image reworked under the same name gets a new URL instead of the old bytes a cache in front of the daemon still holds. - 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.
Entry translation languages
plugins/changelog/lib/entries.mjs owns isEntryTranslationLanguage(lang), the shared boolean predicate for translated entry filenames and API locale normalization. It accepts cs and sk; English uses the original Markdown entry. The translation-language set remains private. The filename parser applies the predicate to its extracted suffix, and registerRoutes applies it directly to the requested locale. An absent, unsupported or unavailable translation uses the English original. The predicate performs only a membership lookup and has no I/O or registration cost.
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.mdwith itscsandsktranslations 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.