> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evox.wraithesports.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Events and host-RPC reference

> The complete list of events a plugin receives and RPC calls it can send to the host.

This page is the complete surface of the evoX plugin protocol: **events** coming from the host to the plugin and **host-RPC** calls going from the plugin to the host. All names are the real contract in `@evox/plugin-protocol`; the renderer/plugin payload is not authoritative, identity and context are derived by the host.

## Contract rules

* Each binding instance is valid only for its own `bindingInstanceId` context. A display call after `action.willDisappear` is rejected with `CONTEXT_EXPIRED`.
* The host-assigned `session.plugin.pluginId` is the only authoritative identity; no other identity is selected through an event/RPC payload.
* RPC calls require a capability. A call that is not declared/granted returns `CAPABILITY_DENIED` (see [Capability APIs](/en/sdk/capabilities)).
* Error behavior is fail-closed; for codes see [Error codes](/en/sdk/errors).

## Received events (host → plugin)

Registered with `client.on(<event>, handler)`. Handlers must be registered before the `client.start()` handshake.

### Plugin lifecycle

| Event | When |
| - | - |
| `plugin.start` | The session was established and `host.hello` received; the plugin is ready to work |
| `plugin.stop` | The session is closing; run handler cleanup and call `client.dispose()` |
| `plugin.suspend` | The plugin was temporarily suspended; do not send new requests |
| `plugin.resume` | Returned from suspension; continue normal processing |

### Device

| Event | When |
| - | - |
| `device.connected` | A device was connected; the payload carries the device identity and size information |
| `device.disconnected` | The device was removed |

### Action binding

| Event | When |
| - | - |
| `action.willAppear` | An action instance became visible on a key/dial (app launch, profile/page switch, user assigned it) |
| `action.willDisappear` | The action instance is no longer visible (profile/page change, assignment deleted) |
| `action.keyDown` | The user pressed the key |
| `action.keyUp` | The user released the key |
| `action.dialDown` | The user pressed the dial |
| `action.dialUp` | The user released the pressed dial |
| `action.dialRotate` | The user rotated the dial; the payload carries `delta` (signed step, e.g. `+1`/`-1`) and/or `direction` (`'LEFT'`/`'RIGHT'`) |
| `action.settingsChanged` | The user changed this binding's settings in the interface; the current `settings` are in the payload. The plugin's own `client.patchSettings` call does not produce this event |

Every event carries `actionId` and `bindingInstanceId` in `event.context`. An `actionId` the plugin does not recognize must be ignored.

```js theme={}
client.on('action.keyDown', async (event) => {
  if (event.context?.actionId !== 'counter') return;
  await client.setTitle(event.context.bindingInstanceId, '1');
});

client.on('action.willDisappear', (event) => {
  counters.delete(event.context?.bindingInstanceId);
});
```

## Host-RPC calls (plugin → host)

Called through the SDK client; each returns a `Promise` and fails closed.

### Key/screen display — `key.display` capability

| SDK method | RPC | Description |
| - | - | - |
| `client.setTitle(context, title)` | `key.setTitle` | Changes the binding title |
| `client.setImage(context, image)` | `key.setImage` | Changes the binding image (manifest-hash `assetId` or a bounded PNG/JPEG/WebP descriptor) |
| `client.setState(context, state)` | `key.setState` | Changes the state of a multi-state action (starting at 0) |
| `client.showSuccess(context)` | `key.showSuccess` | Shows a temporary success indicator |
| `client.showError(context)` | `key.showError` | Shows a temporary error indicator |

```js theme={}
await client.setTitle(context.bindingInstanceId, 'Hazır');
await client.showSuccess(context.bindingInstanceId);
```

Raw file paths or external URLs are not accepted.

### Settings persistence

| SDK method | RPC | Description |
| - | - | - |
| `client.getSettings(context?)` | `settings.get` | Reads the persistent setting of the binding (or plugin-wide without a context) |
| `client.patchSettings(settings, context?)` | `settings.patch` | Partially updates the setting and returns the current settings. The plugin's own write is not echoed back to it as `action.settingsChanged`; that event arrives only when the user changes the setting in the interface |

Every key you write must be declared in the manifest's settings schema: the action's `settingsSchema` for a call with a context, the plugin-wide `settingsSchema` for a call without one. A request containing a key that is not declared in the schema is rejected with `INVALID_REQUEST` (SDK 0.2.0 behavior change; the binding level now follows the same rule as plugin-wide settings). An attempt to write a `secret` or `oauth` field through this path returns `CAPABILITY_DENIED`.

<Warning>Do not write tokens/passwords/client secrets into normal settings. Use the secret/OAuth APIs for credentials.</Warning>

### Plugin-scoped storage — `storage.private` capability

| SDK method | RPC | Description |
| - | - | - |
| `client.storageGet(key)` | `storage.get` | Reads a plugin-private value with a bounded quota |
| `client.storageSet(key, value)` | `storage.set` | Writes the value |
| `client.storageDelete(key)` | `storage.delete` | Deletes the value |

### Network and credentials — `network.fetch` / `secret.manage` / `oauth`

| SDK method | RPC | Description |
| - | - | - |
| `client.networkFetch(input)` | `network.fetch` | A request within the declared HTTPS host/method/byte scope |
| `client.networkFetchWithCredential(input)` | `network.fetchWithCredential` | The host resolves the `{kind:'secret',name}` / `{kind:'oauth',provider}` reference from the vault and injects it into the permitted request; the raw token never returns to the plugin |
| `client.hasSecret(name)` | `secret.has` | Whether the secret exists (returns no bytes) |
| `client.deleteSecret(name)` | `secret.delete` | Deletes the secret |
| `client.beginOAuth(provider)` | `oauth.begin` | Starts the OAuth flow |
| `client.getOAuthStatus(provider)` | `oauth.status` | Reads the OAuth status (returns no token bytes) |

### System and advanced surface

The following calls are defined in the protocol and capability-protected; without a separate SDK shortcut method, they are used through the generic `client.request(<rpc>, params)` path. Each requires the related permission to be declared in the manifest and a host/user grant.

| RPC | Capability | Description |
| - | - | - |
| `system.openExternal` | `system.openExternal` | Opens an HTTPS URL in the default browser |
| `system.openPath` | `system.openPath` | Opens a permitted path on the system |
| `input.sendHotkey` | `input.sendHotkey` | Sends a shortcut key combination to the system |
| `input.typeText` | `input.typeText` | Types bounded text to the system |
| `profile.activate` | `profile.activate` | Activates a permitted profile |
| `profile.write` | `profile.write` | Performs a host-validated profile write |
| `clipboard.read` | `clipboard.read` | Reads bounded content from the clipboard |
| `clipboard.write` | `clipboard.write` | Writes bounded content to the clipboard |
| `action.invoke` | `action.invoke` | Triggers actions of plugins on the permission list |

<Warning>`input.*`, `clipboard.*` and `profile.*` are high-impact surfaces: they are default-deny, declare them with a narrow scope; in T2 they do not work without a user grant.</Warning>

## Notes

* This reference reflects the current protocol version; capability names and limits are normative on the [Capability APIs](/en/sdk/capabilities) page.
* For the public distribution boundary and T1/T2/T3 differences, see [Trust tiers](/en/sdk/trust-tiers) and [Distribution status](/en/sdk/distribution-status).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.