Skip to main content

Module installer

Module installer 

Source
Expand description

The PluginInstaller trait — the method surface later agents implement.

This crate lives at the infra layer and has no access to AppState (bamboo-server, an app-layer crate). Actually registering capabilities — merging into config.json, calling mcp_manager.start_server, appending to prompt-presets.json, writing workflow files — all need AppState (or equivalent handles), so a REAL implementation of this trait has to live in bamboo-server (or a sibling crate that depends on it), implemented by a later agent (see PLUGIN_PLAN.md § Installer-core agent). Because the trait is foreign there and the implementing type is local, that’s a perfectly ordinary downstream impl — no orphan-rule issue.

LocalPluginInstaller below is a reference skeleton that implements everything this crate CAN implement without AppState (manifest validation, platform gating, MCP-entry token resolution, the disposition/upgrade decision, the provides.skills-authoritative check, provenance listing) and returns PluginError::NotImplemented at the exact points that need capability-registration wiring, with a comment enumerating what goes there and citing the exact files. It is a reference/example, not a requirement to reuse verbatim — Wave-2’s installer-core agent may replace it entirely with a type that holds an AppState handle.

§Ownership + upgrade contract (why uninstall is provably safe)

Two invariants make uninstall/upgrade never touch a user’s own entries:

  1. Only plugin-created entries are ever recorded as removable. For the REFUSE-on-conflict capabilities (MCP servers, workflow files) the installer MUST run crate::registry::reconcile_exclusive against the live shared store before touching anything: a declared id/filename that already exists and is not owned by this plugin lands in foreign_conflicts and the install is REFUSED (PluginError::Conflict) — it is never registered and never written into crate::registry::RegisteredCapabilities. So the registered set an InstalledPlugin carries contains ONLY entries this plugin genuinely created; uninstall iterating that set can only ever delete the plugin’s own entries. (Prompt presets are the one exception: they rename on collision via bamboo-server’s ensure_unique_preset_id instead of refusing, and the RENAMED id is what gets recorded.)
  2. Upgrade de-registers what the new version dropped. install with InstallDisposition::Upgrade loads the prior InstalledPlugin, computes crate::registry::RegisteredCapabilities::removed_since (old minus new), de-registers those dropped capabilities, THEN registers the new set and upserts provenance — so a capability the old version had and the new one dropped can’t leak as an orphan.

Structs§

LocalPluginInstaller
Reference skeleton — see module docs. Holds only a bamboo_dir so tests can point it at a tempdir instead of the real ~/.bamboo.

Enums§

InstallDisposition
How PluginInstaller::install must treat a plugin id that is ALREADY installed. Maps directly to the CLI verbs: bamboo plugin install uses Self::FailIfInstalled, bamboo plugin update (or install --force) uses Self::Upgrade.

Traits§

PluginInstaller
The installer method surface. install/uninstall/list are the verbs the CLI (bamboo plugin install/list/remove/update) and the HTTP routes (/api/v1/plugins) both call through.

Functions§

load_previous_for_disposition
Loads prior provenance for plugin_id from installed_json_path and applies the InstallDisposition gate: InstallDisposition::FailIfInstalled errors PluginError::AlreadyInstalled when a COMPLETED entry already exists; InstallDisposition::Upgrade passes through either way. Returns the previous entry (None for a fresh install), which the caller needs for the upgrade drop-diff (BLOCKER 2) and for crash recovery.
on_disk_skill_dirs
Directory names directly under <plugin_dir>/skills/ that contain a SKILL.md (i.e. what discovery would actually pick up in place). Used by preflight_install to enforce that provides.skills is authoritative (MAJOR 4).
preflight_install
Everything an install can validate/resolve WITHOUT touching AppState: manifest validation, platform gating, per-entry MCP resolution (so a malformed entry — e.g. an empty stdio command — fails fast before any registration happens), and the provides.skills / provides.workflows on-disk existence + authoritative checks (MAJOR 4).