The entry is a plain generic function, not a proc-macro. A
`#[ados_plugin]` attribute would expand to little more than
`fn main() { run_plugin::() }`, so the SDK keeps the call site
explicit and skips the macro crate.
### The runner
`run_plugin` parses argv and the environment, connects the IPC client,
builds the `PluginContext`, and drives the hooks. It reads `--socket`,
`--token`, and `--agent-id` off the command line, falling back to the
`ADOS_PLUGIN_SOCKET`, `ADOS_PLUGIN_TOKEN`, and `ADOS_PLUGIN_AGENT_ID`
environment variables (the exact contract the host passes a Rust
binary). The hook order matches the Python runner:
```
on_install -> on_enable -> on_configure(ctx, config) -> on_start
-> (wait for shutdown) -> on_stop -> on_disable
```
`run_plugin_with` takes pre-parsed `RunnerArgs`, for a test or a host
harness that supplies arguments without touching the process
environment.
## `PluginContext`
The object handed to every hook. Each field is a small capability-gated
facade over one IPC client; the facade shapes the request and decodes
the reply, and the host enforces the capability before the call reaches
a handler.
| Field | What it does | Capability |
| ------------------------ | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `ctx.events` | `publish(topic, payload)` and `subscribe(pattern, cb)` on the event bus | `event.publish` / `event.subscribe` |
| `ctx.mavlink` | `send(bytes, component_id)`, `subscribe(msg_name, cb)`, `register_component(id, kind)` | `mavlink.read` / `mavlink.write` / `mavlink.component.*` |
| `ctx.telemetry` | `extend(channel, payload)` to add a channel to the heartbeat the GCS reads | `telemetry.extend` |
| `ctx.peripheral_manager` | `register_*_driver(ref)` for each driver kind, `unregister(handle)`, `claim_camera(path, exclusive)` | `sensor.