Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt

Use this file to discover all available pages before exploring further.

The extension host is the part of a Kael application that owns the plugin lifecycle. It tracks which extensions are loaded, which are active, and what UI contributions are available at any given moment. You interact with it through two complementary types: the lower-level ExtensionHost struct and the higher-level ExtensionHostRuntime that adds process supervision, IPC transport management, and disk-based installation on top.

ExtensionHost vs ExtensionHostRuntime

ExtensionHostExtensionHostRuntime
Defined inplugin.rsextension_host.rs
Manages manifestsYesYes (delegates to ExtensionHost)
Spawns processesNoYes (via ProcessSupervisor)
Manages transportsNoYes
Reads from diskNoYes
Dev-mode loadingNoYes
Use ExtensionHost directly in unit tests or embedded contexts where you control manifest loading manually. Use ExtensionHostRuntime in production application code.

ExtensionHostRuntime

pub struct ExtensionHostRuntime {
    host: ExtensionHost,
    supervisor: ProcessSupervisor,
    extensions_dir: PathBuf,
    transports: HashMap<String, ExtensionTransport>,
    app_id: String,
}

Creating an instance

use kael::ExtensionHostRuntime;

let mut runtime = ExtensionHostRuntime::new("/app/extensions", "com.example.myapp");
new creates the extensions directory if it does not already exist. The app_id string is used when generating per-extension IPC socket paths.

Loading extensions

You have three ways to bring an extension into the host:
Pass a PluginManifest you already have in memory. API compatibility is checked before the manifest is accepted.
use kael::plugin::{ExecutionModel, PluginManifest};

let manifest = PluginManifest::builder(
    "com.example.my-plugin",
    "My Plugin",
    "1.0.0",
    "1.0.0",
    "my-plugin",
    ExecutionModel::ExternalProcess,
)
.build()?;

runtime.load(manifest)?;

API compatibility checking

Before a manifest is accepted by ExtensionHostRuntime::load, Kael checks whether the plugin’s declared api_version is compatible with the host:
// From plugin.rs
pub const HOST_API_VERSION: &str = "1.0.0";

pub fn is_api_compatible(plugin_api_version: &str) -> bool {
    // Exact major-version match required.
    plugin_api_version.starts_with("1.")
}
load calls is_api_compatible and returns an error if the check fails:
// ExtensionHostRuntime::load (extension_host.rs)
pub fn load(&mut self, manifest: PluginManifest) -> Result<()> {
    if !is_api_compatible(&manifest.api_version) {
        anyhow::bail!(
            "extension {} API version {} is incompatible with host {}",
            manifest.id,
            manifest.api_version,
            HOST_API_VERSION
        );
    }
    self.host.load_manifest(manifest)
}
Any plugin targeting a 2.x or 0.x API version is rejected. Only 1.x versions are accepted by the current host.

Extension lifecycle

Once loaded, an extension moves through a well-defined set of states.
1

Loaded

The manifest is stored in the host. is_active is false and process_id is None.
runtime.load(manifest)?;
assert!(!runtime.host.get("com.example.my-plugin").unwrap().is_active);
2

Activated

Call activate to mark the extension as live. For ExternalProcess extensions, the runtime also spawns the child process and sets up the IPC transport.
// Simple activation (in-process / Wasm)
runtime.host.activate("com.example.my-plugin")?;

// Activation with a process attachment
runtime.host.attach_process("com.example.my-plugin", process_id)?;
After attach_process, is_active is true and process_id holds the running process identifier.
3

Deactivated

deactivate sets is_active to false and clears process_id. The host stops routing requests to the extension.
runtime.host.deactivate("com.example.my-plugin")?;
4

Unloaded

unload removes the extension from the registry entirely. It errors if the extension is still active.
runtime.host.unload("com.example.my-plugin")?;

ExtensionInfo

The host stores one ExtensionInfo value per loaded extension:
pub struct ExtensionInfo {
    pub manifest: PluginManifest,
    pub is_active: bool,
    pub process_id: Option<ProcessId>,
    pub load_path: Option<PathBuf>,
    pub dev_mode: bool,
}
Read it with host.get(id):
if let Some(info) = runtime.host.get("com.example.my-plugin") {
    println!("active: {}", info.is_active);
    println!("dev: {}", info.dev_mode);
}

Querying extensions

ExtensionHost provides several read methods:
// All loaded extensions
let all: Vec<&ExtensionInfo> = host.all();

// Only active extensions
let active: Vec<&ExtensionInfo> = host.active();

// Live contribution aggregates (from active extensions only)
let commands   = host.active_commands();   // Vec<&ContributedCommand>
let menu_items = host.active_menu_items(); // Vec<&ContributedMenuItem>
let panels     = host.active_panels();     // Vec<&ContributedPanel>
Contribution aggregates only include contributions from active extensions. An extension that is loaded but not yet activated does not contribute to the command palette, menus, or panels.

Listing and uninstalling extensions on disk

// Enumerate every installed extension directory
let installed: Vec<(String, PathBuf)> = runtime.list_installed()?;
for (id, path) in installed {
    println!("{} at {}", id, path.display());
}

// Remove an extension from disk (must be deactivated first)
runtime.uninstall("com.example.my-plugin")?;
uninstall errors if the extension is currently active. Deactivate it first.

ExtensionHost standalone usage

When you do not need process management, use ExtensionHost directly:
use kael::plugin::ExtensionHost;

let mut host = ExtensionHost::new();

host.load_manifest(manifest.clone())?;
host.activate("com.example.my-plugin")?;

// Query contributions
for cmd in host.active_commands() {
    println!("command: {} ({})", cmd.title, cmd.id);
}

host.deactivate("com.example.my-plugin")?;
host.unload("com.example.my-plugin")?;
ExtensionHost implements Default, so ExtensionHost::default() is equivalent to ExtensionHost::new().

Build docs developers (and LLMs) love