6.2 KiB
Android Plugins
Hermes Android exposes opt-in, plugin-provided pages through the authenticated upstream Dashboard connection. The product name is Plugins. The implementation borrows the contribution model from the official Hermes Desktop Plugin SDK while using an Android-specific execution boundary.
Security boundary
Android never downloads or evaluates plugin JavaScript, Kotlin, bytecode, WebViews, or arbitrary Compose code. A backend plugin supplies a bounded, versioned JSON document and the app renders it with host-owned Compose components. Documents cannot provide network URLs, Android intents, filesystem paths, gateway calls, secrets, or authentication material.
The Dashboard plugin catalog establishes plugin identity. All follow-up reads and
writes stay below /api/plugins/<plugin-id>/...; the client rejects traversal,
queries, fragments, absolute URLs, and cross-plugin paths. Enablement and grants are
stored locally per connection, effective profile, and plugin.
Read-only pages are available after the user enables a plugin. Mutating actions also
require the local plugin.api.write grant. Actions with a confirmation string require
an additional per-use confirmation in the app. The app never gives plugins direct
access to session tokens or cookies.
Discovery contract
- Read the authenticated catalog at
GET /api/dashboard/plugins. - Probe catalog entries that expose an API at
GET /api/plugins/<plugin-id>/mobile/manifest. - Accept only manifests whose
idmatches the catalog identity, whoseschema_versionis supported, and whosemin_host_apiis compatible. - Load enabled page contributions through their plugin-scoped document endpoint.
The v1 manifest shape is:
{
"schema_version": 1,
"id": "weather",
"display_name": "Weather",
"description": "Local forecasts and alerts",
"version": "1.0.0",
"min_host_api": 1,
"default_enabled": false,
"requested_capabilities": [
{ "id": "plugin.api.write", "reason": "Save alert preferences" }
],
"contributions": [
{
"id": "forecast",
"surface": "page",
"title": "Forecast",
"document": { "method": "GET", "path": "mobile/pages/forecast" }
}
]
}
Declarative page surface
Schema v1 supports complete scrollable pages composed from groups, cards, text, badges, buttons, text inputs, toggles, progress indicators, opaque plugin-scoped images, dividers, and spacers. Literal values and state bindings drive conditional visibility and enabled state. Host-owned fade, scale, vertical-slide, and highlight transitions make pages reactive and animated without allowing executable UI code. They become immediate static updates when system animations are disabled or touch exploration is active.
Button actions may call a relative endpoint in their own plugin namespace. Write requests receive both the action's declared arguments and the page's current bound state, then the host reloads the document so the plugin remains authoritative. The host validates document size, nesting, identifiers, bindings, text lengths, animation values, and asset references before rendering.
Future schema versions can add elements or contribution surfaces deliberately. An unknown v1 element is rejected rather than interpreted as code or rendered in a WebView.
Relationship to chat cards
Existing model-generated CARD:{json} content remains presentation-only chat output.
It does not identify a plugin, inherit plugin grants, or gain access to plugin action
endpoints. Plugin authority always begins with the authenticated Dashboard catalog and
the matching mobile manifest.
Live agent-created pages
When the optional Hermes-Relay plugin is installed, the active Hermes agent can use
the relay_plugin_draft tool to create or replace a generated declarative page. The
tool accepts the same bounded schema as Android, stores JSON atomically below
HERMES_HOME/mobile-plugins, and rejects every action.request. Generated previews
therefore cannot reach Relay management APIs or acquire executable backend behavior.
The contribution ID git is reserved for the Relay plugin's native Git workspace;
generated drafts cannot shadow or duplicate that route.
Android presents that reserved contribution as a first-class native surface rather
than a generic declarative page. When the live plugin API confirms Git is available,
Chat can show a compact branch/change rail and a dot-only context action; Chat →
Show Git workspace in Chat hides those two Chat affordances without disabling the
workspace. The full native workspace remains reachable from Settings and Plugins and
owns repository/branch selection, diffs, staging, confirmed destructive actions,
commits, and remotes. Session git_repo_root/cwd metadata selects an exact matching
repository when possible; ambiguous catalogs require an explicit user choice. Mobile
discovery alone is not treated as runtime readiness: a missing Git API route renders a
retryable unavailable state instead of exposing the raw Dashboard error.
The Relay mobile manifest exposes drafts as preview pages under the authenticated
hermes-relay plugin namespace. Android polls the catalog every five seconds while
the Plugins hub is visible and polls a visible generated page every five seconds.
No catalog or page polling continues after that surface leaves the foreground.
Publishing and persistent removal are user decisions:
- The agent creates or updates a draft and may list it.
- Android displays it as Draft preview.
- The user enables the Relay Plugins surface and its scoped write grant.
- Keep publishes the exact current draft; Remove requires confirmation.
- The agent-side publish/remove tools return
approval_requiredinstead of applying persistent mutations themselves. Only a session-lifecycle draft may be removed directly by the agent because that operation is local and reversible by redrafting.
The first store version is host-local and uses lifecycle labels, but does not isolate draft visibility between simultaneous Hermes sessions. True task-private previews, profile/connection publication precedence, revision-bound approvals, and push catalog invalidation remain future protocol work. The current polling path is deliberately small and requires no upstream fork.