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

# Event hooks

> The events the host pushes to a GCS plugin and the requests the plugin can send.

On the GCS half the host runs the bridge; the plugin consumes two flavors
of message:

* **Events**: pushed from the host, no response expected
  (`config.changed`, `theme.changed`, `telemetry.<topic>`).
* **Responses**: returned by the host in reply to a plugin **request**.

Plugins subscribe to events and send requests; they never receive
unsolicited requests.

## Events the host pushes

| Method              | Capability                    | Args                                      | When                                          |
| ------------------- | ----------------------------- | ----------------------------------------- | --------------------------------------------- |
| `theme.changed`     | none                          | `Record<string, string>` of CSS variables | Initial mount and on every host theme change. |
| `config.changed`    | none                          | the plugin's full config object           | Operator saves a new config.                  |
| `telemetry.<topic>` | `telemetry.subscribe.<topic>` | topic-specific payload                    | After the plugin subscribed to `<topic>`.     |

Subscribe via `ctx.client.on("config.changed", handler)` or the typed
wrappers `ctx.config.onChange`, `ctx.theme.onChange`, and
`ctx.telemetry.subscribe`. For any other host-pushed topic (for example
the `video.overlay` host props the host forwards to a video overlay
iframe, or an agent plugin's state read-back), use the generic
`ctx.events.subscribe(topic, handler)`, which returns an unsubscribe
function and does no request/response round-trip.

## Requests the plugin can send

The SDK context exposes these high-level wrappers. Each delegates to a
single `PluginClient`; drop to `ctx.client.request(method, capability,
args)` for anything else.

| Wrapper                                   | Method                 | Capability                     | Returns              |
| ----------------------------------------- | ---------------------- | ------------------------------ | -------------------- |
| `ctx.telemetry.subscribe(topic, handler)` | `telemetry.subscribe`  | `telemetry.subscribe.<topic>`  | unsubscribe function |
| `ctx.command.send(command, args)`         | `command.send`         | `command.send`                 | host-defined         |
| `ctx.notifications.publish(payload)`      | `notification.publish` | `ui.slot.notification-channel` | host-defined         |
| `ctx.recording.mark(payload)`             | `recording.mark`       | `recording.write`              | host-defined         |
| `ctx.mission.read(missionId)`             | `mission.read`         | `mission.read`                 | mission body         |
| `ctx.mission.write(update)`               | `mission.write`        | `mission.write`                | host-defined         |

The host registry also recognizes `telemetry.unsubscribe`, `recording.start`
and `recording.stop` (capability `recording.write`), `events.subscribe` and
`events.publish` (`event.subscribe` / `event.publish`, both require a
`topic` arg), `cloud.read` and `cloud.write`, plus three always-allowed
methods that need no capability: `ping`, `notify`, and `i18n.t`. A method
that is not in the registry is rejected with `method_unknown`.

The host re-resolves the required capability from the method (and, for
telemetry, from the topic) and checks the granted set. The plugin never
computes the capability id itself.

## Wire shape

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
interface RpcEnvelope<TArgs = unknown> {
  id: string;
  type: "request" | "response" | "event";
  method: string;
  capability: string;
  args: TArgs;
  version: 1;
  error?: { code: string; message: string };
}
```

The plugin never builds these by hand. The SDK's `PluginClient` generates
the `id`, attaches the protocol version, and waits for the correlated
response.

## Error envelopes

When the host rejects a request it returns a response with
`error: { code, message }`. The SDK throws a `HostError` whose `code`
field carries the machine-readable code:

| Code                | Meaning                                                                                                                                                                                              |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `permission_denied` | The required capability is not in the granted set.                                                                                                                                                   |
| `capability_denied` | The per-request capability token failed validation, or its claimed capability set does not cover the request. The message carries the reason after a colon (e.g. `capability_denied:token_expired`). |
| `method_unknown`    | The method is not in the host's registry.                                                                                                                                                            |
| `schema_invalid`    | Args failed schema validation.                                                                                                                                                                       |
| `handler_unset`     | The host knows the method but no handler is wired.                                                                                                                                                   |
| `handler_error`     | The host's handler threw; the message carries the cause.                                                                                                                                             |

Branch on `code`, not on `message`. Messages are for log lines.

<Note>
  There is also an `origin_mismatch` error, but the host never sends it to
  the plugin. When a message arrives from a window that is not the trusted
  iframe, the bridge drops it and records a security event host-side rather
  than replying. A plugin will never see it on the wire.
</Note>

## Theming

`theme.changed` arrives once on mount and again on every theme toggle.
The payload is a `Record<string, string>` of CSS variables. Apply them by
walking the entries:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
ctx.theme.onChange((vars) => {
  for (const [key, value] of Object.entries(vars)) {
    document.documentElement.style.setProperty(key, value);
  }
});
```

The SDK does not auto-apply because plugins choose how granular they want
to be (full root, scoped to a panel, or ignored).

## i18n

Plugin authors ship a locale bundle in their archive; the host streams
the active bundle to the plugin on mount. The SDK formats keys with
`ctx.i18n.t(key, params)`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
ctx.i18n.t("anomaly.cellLow", { voltage: 3.4 });
// "Cell low: 3.4 V"
```

Missing keys fall back to the key itself; missing parameters render the
`{name}` placeholder so it is visible in QA.

## See also

* [Event hooks and bus](/developers/event-hooks-and-bus)
* [Permissions](/developers/permissions)
* [SDK TypeScript](/developers/sdk-typescript)
