Extensions

Extensions

What WASM component extensions can do, how the host loads and sandboxes them, and the WIT contract.

An extension is a WebAssembly component that implements the WIT world oino:extension/guest@2.0.0. The host runs it in wasmtime with only the capabilities its manifest asks for. Extensions can:

  • register tools, slash commands, keyboard shortcuts, CLI flags and settings pages;
  • subscribe to session and agent events, and veto or rewrite what they see;
  • run dialogs, notifications, status lines, widgets and panels in any client;
  • serve calls from other extensions;
  • spawn nested agent sessions (subagents), subprocesses and background tasks;
  • make HTTP requests through the host, with allowlisted hosts;
  • read and append session entries, switch models, trigger compaction;
  • act as providers and virtual models.

The Rust guest SDK makes all of this idiomatic; other languages build the same WIT world with component-model toolchains (Rust walkthrough, packaging).

Load order#

Packages are directories with an extension.json manifest and a component.wasm. Later sources replace earlier packages with the same id:

  1. Distribution: the share dir the installer owns (OINO_DIST_EXTENSIONS overrides, an empty value disables). Bundled providers and first-party extensions live here.
  2. Global: ~/.oino/extensions/<package>/.
  3. Project: <project>/.oino/extensions/<package>/, trusted projects only.
  4. Command line: -e <dir> per package, wins over everything.

-ne disables all extensions for a run (providers stay, explicit -e still loads), --exclude-extension <id> drops specific ids, and disabledExtensions in settings persists the decision.

The sandbox#

Each component runs in its own wasmtime instance. Every permission defaults to denied except UI: network hosts, process spawning, file system access, secrets, nested agents, environment variables and OS integration are only granted when the manifest lists them. A missing grant fails the import at call time with permission denied: <permission>. Bundled packages get exactly what they declare, nothing more.

Extension state lives under ~/.oino/state/<extension-id>/, split into global, per-workspace and per-session scopes. Nothing is written into your project unless a tool does it. Memory (default 512 MiB, max 2048) and hook deadlines are capped in the manifest.

If two consecutive starts fail, the next start runs with extensions off and providers on, and names the recorded suspects in a warning banner. A successful start resets the counter.

The WIT contract#

oino:extension@2.0.0 is defined in wit/oino-extension/ in the repository and bundled inside the oino-extension crate. Data values (messages, models, events, tool results) cross the boundary as canonical JSON with fixed shapes; host capabilities are typed interfaces:

Interface What it offers
guest (export) init, on-event, run-tool, run-command, shortcuts, renderers, route-model, arbiter hooks
provider (export) stream, list-models, OAuth login/refresh (provider components)
registry tools, commands, shortcuts, flags, renderers, settings namespaces, providers, virtual models, services
session read entries, branch and tree, append entries, send messages, session index
control model and thinking level, run state, context usage, compaction, tool control, nested tool calls
ui dialogs, notify, status, widgets, panels, editor text
models completions, streaming, summaries, credentials
agents nested in-process agent sessions
process, tasks subprocesses, background tasks
http outbound HTTP with allowlists, proxy, credential injection
fs, kv, secrets, timers, bus, services, log, host-info state, storage, timers, cross-extension messaging, diagnostics

The guest never talks to the network or disk directly; WASI is limited to the preopened scopes the host grants. Async is component-model async: event handlers, tools and commands of one extension run concurrently.

First-party extensions#

The distribution ships a set of components beyond the providers: a permission gate with plan mode, long-horizon goal and task-loop modes, memory, a skill registry, subagents, background tasks, an MCP client, web search, notifications, a kanboard, a lossless compactor and more. Each is an ordinary package built with the same SDK, which makes them useful reference code; browse them under extensions/ in the repository.

Known gaps#

models.classify and models.generate-image return unsupported, and control.shutdown is recorded but no client exits on it. These are host-side gaps; guest code that calls them gets an error, not a panic.