Manifest reference

Applies to: StablEdge Desktop 0.4.4, plugin manifest protocol v3 (schema_version: 3)

Every plugin package has a plugin.json in its root: the plugin manifest. The host validates it against the v3 JSON Schema at install, then runs the cross-field checks (permission combinations, file hashes, the icon and so on). Any field the schema does not define is refused.

0.4.5 adds pages, profile_items, editors and windows, plus the ui.window* permissions; see Surfaces (Draft).

Top-level fields

FieldRequiredType and constraintsNotes
schema_versionYesconstant 3Manifest protocol version.
plugin_idYes^[a-z][a-z0-9.-]{2,63}$Globally unique, unchanging plugin identifier; a reverse domain name is recommended, such as com.example.my-notes.
nameYesstring, 1–80Plugin name.
display_nameNostring, 1–80Name shown in the interface; name is used when this is absent.
descriptionNostring, 1–200One-line summary, shown in the plugin list and the install preview.
plugin_typeNoworkspace | knowledge | skill | integration | utilityPlugin category: workspace, knowledge source, Skill, integration, utility.
iconSee notes{ "resource_id" }Points at a PNG resource in contributions.resources. New packages must provide it; see Icon.
i18nNoobjectTranslations the plugin carries itself; see Translations.
versionYessemantic version, such as 1.2.0, 1.2.0-beta.1Plugin version.
minimum_app_versionYesstring, 5–32Lowest desktop version required; installing on anything older is refused.
contributionsYesobjectThe interfaces, Skills and resources the plugin provides; see Contributions.
workersYesarray, ≤16Background worker processes; write [] when there are none.
triggersNoarray, ≤32Scheduled or lifecycle triggers.
event_channelsNoarray, ≤16Long-lived event channels.
credentialsNoarray, ≤16Credentials the host keeps.
requested_permissionsYesarray, ≤64Permissions requested; see Permissions.
required_servicesYesarray, ≤32Services the plugin depends on; write [] when there are none.
ai_runtimeNoobjectAn AI runtime the host runs; must come together with the runtime.ai permission and is open to the official source only.
platformsYesat least one of windows-x86_64 | macos-aarch64 | macos-x86_64Supported platforms; installing on a platform that is not listed is refused.
filesYesobject: path → 64-character lowercase hexSHA-256 of every file in the package, forming the sealed file set.
publisherYesstring, 1–120Publisher name.
publisher_key_idNo^[A-Za-z0-9._-]{3,128}$Identifier of the signing public key; comes as a pair with signature.
signatureNostring, 40–4096Manifest signature; comes as a pair with publisher_key_id. The packager generates it, so never write it by hand.

Local identifiers

Every id in a manifest (workspace, worker, Skill, resource, trigger and so on) follows one rule: ^[a-z][a-z0-9_-]{1,47}$, so it starts with a lowercase letter and is 2–48 characters long. Two contributions of the same kind cannot share an id.

Icon

  • Declare a resource in contributions.resources with media_type set to image/png, then refer to it from icon.resource_id.
  • The image must be exactly 200×200 pixels, no larger than 500 KiB, and listed in files.
  • At pack time a missing icon, a file that is not a PNG, the wrong size or an icon left out of files refuses the package outright (PLUGIN_MANIFEST_INVALID); at install an invalid icon resource reports PLUGIN_ICON_INVALID.
  • Older packages that have shipped without an icon still install, but a 200×200 icon is required the next time you pack.
  • Images and fonts used by the workspace and top bar pages go straight under payload/ui/ and do not need to be declared in resources.

Translations

i18n lets a plugin carry its own translations of its name, summary and interface text. Writing into the desktop app's language packs is neither needed nor allowed.

FieldRequiredConstraintsNotes
default_localeYes^[a-z]{2}(-[A-Z]{2})?$Default language, such as zh-CN.
languagesYesarray, 1–32One entry per language.
languages[].localeYesas aboveLanguage tag.
languages[].labelYes1–40Language name, such as "Simplified Chinese".
languages[].display_nameYes1–80The plugin's display name in that language.
languages[].descriptionNo1–200The summary in that language.
languages[].workspacesNoobject: workspace id → { "title" }Workspace titles in that language.
languages[].ui_catalogNopayload/i18n/*.jsonInterface text dictionary, read in the interface with i18n.t().

At least one contribution

A manifest must provide at least one of knowledge_providers, workspaces, skills or resources in contributions. Because a new plugin has to declare its icon through resources, a plugin with only a top bar item and no workspace is valid too.

Contributions

Every entry point, resource and Skill file must live under payload/ and appear in files.

workspaces: the workspace interface

Up to 16 entries. Each is an HTML page that runs in a sandboxed iframe; the API is described in Workspace and Bridge API.

FieldRequiredConstraintsNotes
idYeslocal identifierWorkspace identifier.
titleYes1–80Workspace title.
entrypointYespayload/ui/*.htmlEntry page.
bridge_versionYesconstant "1.0"Workspace Bridge version.
allowed_placementsYesat least one of replace_files | replace_editor | replace_bothAllowed placements: replace the file area, replace the editor area, or both.
default_placementYesas above, a single valueDefault placement.
workerNoreferences workers[].idThe worker the interface calls through worker.invoke.
dropsNoobjectAccepts dropped files; requires storage.blob. The host stores the file in the plugin's blob storage and then tells the interface, so the plugin never receives a system path.
drops.extensionsYes*1–16 items, ^[a-z0-9]{1,16}$Accepted extensions, without the dot.
drops.max_filesYes*1–16Most files in one drop.
drops.max_bytesYes*1–1073741824Most bytes in one drop.
capture_providersNo≤8 itemsProvide screenshots of the interface to the Agent; requires agent.capture and storage.blob.
capture_providers[].idYes*local identifierScreenshot provider identifier.
capture_providers[].descriptionYes*1–160Tells the Agent what the screenshot shows.
capture_providers[].max_long_edgeYes*256–4096Longest edge of the screenshot in pixels.
connectNo≤64 http(s) originsSee Network declarations.

Fields marked * are required whenever their parent object appears.

topbar_items: top bar items

Up to 16 entries, shown in the top bar reserved for workspace plugins. An item whose side is full takes the whole row, and plugins in the same section cannot be enabled at the same time.

FieldRequiredConstraintsNotes
idYeslocal identifierTop bar item identifier.
sideYesleft | center | right | fullWhich section it sits in.
labelYes1–32Text shown.
tooltipNo1–160Hover text.
priorityNo-100–100, default 0Sort order within the same section.
workspaceNoreferences a workspace idThe workspace that opens when the item is clicked.
workerNoreferences a worker idThe worker that supplies the data; comes together with method.
methodNostarts with ^topbar\.Worker method the host calls on a timer.
entrypointNopayload/ui/*.htmlA small embedded page; comes together with bridge_version.
bridge_versionNoconstant "1.0"Bridge version the embedded page uses.
min_widthNo48–600, default 48Minimum width in pixels.
max_widthNo48–600, default 280Maximum width in pixels.
refresh_secondsNo1–300, default 15How often method is called.
connectNo≤64 http(s) originsSee Network declarations.

Each top bar item must take one of these three shapes:

  • Static: only display fields such as label, with no worker, method, entrypoint or bridge_version.
  • Worker-driven: worker and method together, with no entrypoint.
  • Embedded page: entrypoint and bridge_version together, with no method.

skills: Skills

Each entry is { "id", "entrypoint" }, where entrypoint is payload/agent/*.md. A Skill file tells the Agent how to read the context the plugin publishes and which actions it may send. Skill files take part in hash and signature checks, but gain no extra tool permissions by doing so.

The schema allows up to 16 entries; in practice ship one Skill file per plugin and keep payload/agent/ holding only that file.

resources: resources

Up to 64 entries, with all three fields required: id (local identifier), path (a file under payload/resources/) and media_type (in type/subtype form, 3–127 characters). Resources are files for the host to read, and are used today by the root icon and ai_runtime.

knowledge_providers: knowledge sources

Up to 16 entries, each { "id", "worker" }, where worker references workers[].id.

This contribution is only validated in the manifest for now and the host has no runtime consumer yet. Do not build features that depend on it.

Network declarations (connect)

Workspaces and top bar items can list the origins they will reach in connect (only http:// or https:// plus a host and an optional port, with no path), so the install page can show them. Declaring connect means you must also request net.fetch with worker set to host, otherwise the install is refused. When you configure the address yourself, connect can be left out.

"connect": ["https://api.example.com"]

workers: Workers

Up to 16 entries. How a worker runs and the protocol it speaks are covered in Worker.

FieldRequiredConstraintsNotes
idYeslocal identifierWorker identifier, referred to by permissions, workspaces and triggers.
entrypointYespayload/worker/*Entry script.
protocol_versionYesconstant "2.0"Worker protocol version.
isolationYessandboxed_plugin | dev_unrestrictedIsolation level. A dev_unrestricted worker runs unrestricted only after you grant This-computer mode.
activationYeson_demand | on_startupStart when needed, or with the app. on_startup only starts the process and never runs business logic by itself.
resource_limits.memory_mbYes64–2048Memory limit (MB).
resource_limits.cpu_percentYes1–100CPU limit (%).
resource_limits.max_inflightYes1–64Most requests handled at once.
execution_policyNosandbox | trusted, default sandboxtrusted needs your confirmation at install, still runs in the sandbox and cannot start child processes.

Set resource limits to what you actually need, and do not raise them blindly to work around a problem.

required_services: services you depend on

FieldRequiredConstraintsNotes
service_idYes^[a-z][a-z0-9.-]{2,127}$Service identifier, such as com.stabledge.ocr.
versionYes1–64Semantic version range, such as ^1.0.0.
workerYeslocal identifierThe worker that calls the service; that worker must also request service.invoke.
optionalNoboolean, default falseWhen true, a missing service does not block the install and the plugin has to degrade on its own.

Background tasks, events and credentials

Use the three declarations below when a plugin has to run with no interface open and no Agent messages: scheduled sync, catching up once after startup or wake, and listening to a push stream. Background work is always scheduled by the host: when the time comes or an event arrives, the host calls the worker operation you declared. Do not run your own setInterval or keep your own socket inside a worker.

triggers

FieldRequiredConstraintsNotes
idYeslocal identifierTrigger identifier.
kindYesinterval | startup | resumeOn an interval, after the app starts, or after the system wakes.
workerYeslocal identifierThe worker that runs it.
operationYes^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$The operation to call; it must appear in the methods the worker returns at handshake.
minimum_interval_secondsSee notes60–604800Required when kind is interval, and must be left out for the other kinds.
default_enabledNoboolean, default falseWhen false, you enable it yourself under Background tasks on the plugin detail page.
catch_upYesskip | onceAfter missing several cycles, do not catch up, or catch up at most once.
"triggers": [
  {
    "id": "sync",
    "kind": "interval",
    "worker": "main",
    "operation": "sync.incremental",
    "minimum_interval_seconds": 900,
    "default_enabled": false,
    "catch_up": "once"
  }
]

event_channels: event channels

The host keeps the connection open (connecting, heartbeat, reconnecting) and calls the declared operation once for every event it receives. The worker must request events.connect.

FieldRequiredConstraintsNotes
idYeslocal identifierChannel identifier.
kindYessse | websocket | webhookwebhook means a cloud Webhook relay: your own service receives a third-party webhook and pushes it to the host over wss://.
workerYeslocal identifierThe worker that handles events.
operationYesas for triggersThe operation that handles events.
urlYesURI, ≤2048sse requires https:// and the other two require wss://. During local development http:// and ws:// are allowed for 127.0.0.1, localhost and [::1].
credential_refsNo≤8 itemsCredential ids injected when the connection is made.
protocolsNo≤8 itemsWebSocket subprotocols.
default_enabledNoboolean, default falseThere is no separate switch for event channels in the interface yet, so this field decides whether the connection is made.

Delivery is at least once: an operation that returns successfully confirms the event, while an error or a timeout replays it after reconnecting. Make your handling idempotent by event id.

credentials

You fill in a credential on the plugin detail page or complete an authorization, and the host keeps it in the system keychain. Workers, the Agent and the interface cannot read the plain value; they can only refer to it and let the host inject it into a request. A worker that declares credentials must request net.fetch or events.connect.

FieldRequiredConstraintsNotes
idYeslocal identifierCredential identifier.
labelYes1–80Name shown to you.
kindNostatic | oauth2, default staticA static secret or OAuth2. When oauth2, the oauth2 object is required.
workerYeslocal identifierThe worker that uses the credential.
allowed_originsYes1–32 https:// originsInjected only when the request target is in the list.
injection.locationYesheader | queryWhere the value is injected.
injection.nameYes^[A-Za-z][A-Za-z0-9_-]{0,63}$Request header or query parameter name.
injection.prefixNo≤64, empty by defaultValue prefix, such as Bearer .

OAuth2 credentials

The host handles PKCE, token refresh and rotation; on a 401 it forces a refresh and retries once.

FieldRequiredConstraintsNotes
authorization_endpointYeshttps://, with no query or fragmentAuthorization address.
token_endpointYesas aboveAddress to exchange for a token.
revoke_endpointNoas aboveAddress to revoke a token.
client_idYes1–512Client identifier.
scopesNo≤64 itemsScopes requested.
callback_modeYescustom_scheme | loopbackCallback style: a custom scheme, or a loopback address on this machine.
client_authNonone | client_secret_post | client_secret_basic, default noneHow the client authenticates.
token_request_encodingNoform | json, default formEncoding of the token request body. json cannot be used when token_method is get.
token_methodNoget | post, default postToken request method.
responseNoobject whose values are JSON PointersFor a non-standard response, say where access_token, refresh_token, expires_in, token_type, error, error_description and metadata are found.
authorization_paramsNo≤32 key-value pairsExtra parameters for the authorization request.
token_paramsNo≤32 key-value pairsExtra parameters for the token request.
Do not put tokens in KV, in a trigger's input, in a cursor, in a log, or in a long-lived cache in worker memory.

ai_runtime

ai_runtime lets the host fetch source, build a Python environment and run a model runtime for you; it must come together with the runtime.ai permission. It is open to the official source only, and a package from a third-party publisher is refused outright. When a third-party plugin needs a model or heavy computation, use a global Worker service, or let the user run a local HTTP service.

files: the sealed file set

  • Keys are relative paths separated by / and starting with payload/; values are the SHA-256 of the original file bytes (64 lowercase hex characters).
  • You cannot leave a file out or list an extra one, and absolute paths, .., backslashes and symbolic links are not allowed.
  • Pack again after every change under payload/; the old hashes and signature stop working at once. Do not edit files by hand.

v1 and v2 manifests

The desktop app still installs v1 and v2 manifests, but only for older plugins and migration; new plugins always use v3.

VersionWhat it offers
v1Knowledge source plugins only; top-level entrypoint and capabilities; a signature is required.
v2Adds workspaces and agent_integration, and capabilities grew workspace and Agent event capabilities; signature is required.
v3Replaces capabilities with contributions, workers and requested_permissions; supports the top bar, background tasks, credentials and service dependencies; allows unsigned developer packages.
On this page