|
| 1 | +# 0034. Provider plugin registry |
| 2 | + |
| 3 | +Status: accepted. Umbrella #3747; contract and registry #3755. |
| 4 | + |
| 5 | +## Rules at a glance |
| 6 | + |
| 7 | +- One contract. A provider plugin is a factory that returns `{ apiVersion: 2, provider, credentials, |
| 8 | + connection?, setup(host) }`. `setup` returns `{ runtime, platformModule, dispose? }` or |
| 9 | + `{ webDriver, dispose? }`. A `{ webDriver }` result stays a data descriptor the host interprets. |
| 10 | +- One registry. Sources compose in the order built-in, store, embedder. Built-in providers use the |
| 11 | + same shape as an installed plugin, and one function validates what every factory returns. |
| 12 | +- Plugins import only types from agent-device. The type-only contract is `agent-device/plugins`; |
| 13 | + there is no runtime helper, and the contract does not depend on a plugin's module URL. |
| 14 | +- The first registration of a provider id claims it, even when that registration fails. Later ones |
| 15 | + are recorded as failed. `cloud` and `proxy` are routing identities no plugin can claim. |
| 16 | +- A plugin that fails to enumerate, load, validate or set up is recorded and skipped. The daemon |
| 17 | + starts with the rest. The inventory lists every plugin as `active` or `failed`, with a typed |
| 18 | + `error.reason`, and is published in `daemon.json` and by `agent-device daemon status`. |
| 19 | +- The plugin set is fixed for a daemon's lifetime. A plugin change takes effect on the next daemon. |
| 20 | +- v1 plugins (`agentDevicePlugin.apiVersion: 1`) without a `./agent-device` export are dispatched by |
| 21 | + their manifest before any code runs and load through an adapter until 0.22 removes them (#3759). |
| 22 | + A package that keeps the manifest and also exports `./agent-device` loads as v2; hosts without |
| 23 | + v2 still read the manifest. |
| 24 | + |
| 25 | +## Context |
| 26 | + |
| 27 | +Three things loaded providers: a hand-kept `RESERVED_PLUGIN_PROVIDERS` list, a second list of |
| 28 | +providers the daemon must have a runtime for, and a store that treated one bad plugin as a daemon |
| 29 | +startup failure. Each provider kind also needed its own path for credential fingerprints and for |
| 30 | +`connect` capabilities, which read manifests the bundled providers do not have. An embedding host |
| 31 | +had to fake the store to load a plugin from its own dependencies (#3747). |
| 32 | + |
| 33 | +## Decisions |
| 34 | + |
| 35 | +- **A factory takes options.** The store calls it with `config.json` `plugins[name].options`; a JS |
| 36 | + host calls it directly with its own, such as credentials. A plugin that takes credentials as |
| 37 | + options declares no environment variables for them, so they stay out of the environment |
| 38 | + fingerprint. |
| 39 | +- **A package exports its entry as `./agent-device`.** The host resolves it with import |
| 40 | + conditions and falls back to the package root only when the subpath is absent. A subpath whose |
| 41 | + import or setup throws never falls back. A v2 package needs no manifest. |
| 42 | +- **The registry owns identity, not the retired lists.** Duplicate ids and bundled ids are one |
| 43 | + question answered by claim order. Credential variables, per-backend narrowing and connect |
| 44 | + capabilities are read from the registry's plugins, not from manifests. A v1 manifest still |
| 45 | + supplies them for a v1 plugin. |
| 46 | +- **Failure is data.** A failed plugin is an inventory entry, not an exception. Behavior keys on |
| 47 | + `error.reason`, never on the message. |
| 48 | +- **A client reads a daemon's inventory from `daemon.json`.** The client credential fingerprint |
| 49 | + takes variable names from it and falls back to v1 manifests. A v2 plugin declares its connect |
| 50 | + capabilities in code, so a client loads the store, without setting up a runtime, before it reads |
| 51 | + them. |
| 52 | + |
| 53 | +## Daemon ownership |
| 54 | + |
| 55 | +The target is that starting or replacing a daemon is an explicit act, not a side effect of a client |
| 56 | +call. Today a client call can still start a daemon; #3754 makes that explicit for the client |
| 57 | +lifecycle, and #3757 adds `agent-device/host`, where an embedder owns its daemon and passes its |
| 58 | +plugins as the third source. |
| 59 | + |
| 60 | +## Alternatives refused |
| 61 | + |
| 62 | +- **Keep the manifest as the contract.** It cannot describe options-dependent credentials, and a |
| 63 | + bundled provider has no package to hold one. |
| 64 | +- **A runtime helper such as `definePlugin`.** It would make plugins depend on a runtime export of |
| 65 | + agent-device and its version, which is what a type-only contract avoids. |
| 66 | +- **Fail the daemon on a bad plugin.** One broken optional package then takes every provider down. |
0 commit comments