Files
kandev/apps/web/lib/plugins/boot.ts
T
Kandev Agent c53f699714 feat: add plugin system with grpc runtime and native ui plugins
- go plugin backends ship as release tarballs (per-platform binaries, checksums) that
  kandev installs from url/upload or filesystem sideload+sync, then spawns and
  supervises via hashicorp/go-plugin (unix socket / loopback tcp+automtls)
- strict typed grpc contract (kandev.plugin.v1): DeliverEvent/InvokeTool/HandleWebhook
  plus a capability-gated host service (state, secrets, emit); author sdk in
  pkg/pluginsdk; packaging cli in cmd/plugin-pack
- native frontend bundles: window.registerKandevPlugin + registry/host loader with
  dynamic routes, nav items, slots and ws handlers; plugins settings page with
  install dialog, sync button and enable/disable/uninstall
- gated behind the plugins feature flag (prod off, dev/e2e on); covered by unit and
  integration tests plus playwright e2e against a real packaged fixture plugin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:55:45 +01:00

44 lines
1.8 KiB
TypeScript

/**
* Boots the plugin host from the app's boot payload
* (docs/plans/plugins/PLUGIN-API.md). Called once from the app
* bootstrap once the store + boot payload are both available.
*/
import type { StoreApi } from "zustand";
import type { AppState } from "@/lib/state/store";
import type { BootPayload } from "@/src/boot-payload";
import { buildHostApi } from "./host-api";
import { installPluginGlobal, loadPlugins } from "./host";
/** Store instances that have already had `loadPlugins` triggered — boot is a one-shot per store. */
const bootedStores = new WeakSet<StoreApi<AppState>>();
/** Reads the resolved light/dark theme applied to the document by AppThemeProvider. */
function readResolvedTheme(win: Window = window): "light" | "dark" {
return win.document.documentElement.classList.contains("dark") ? "dark" : "light";
}
/**
* Installs `window.registerKandevPlugin` and loads every plugin in
* `bootPayload.plugins`, building each one's `PluginHostApi` from `storeApi`.
*
* No-op when `feature` (the `plugins` feature flag) is off, or when the boot
* payload carries no plugins. Idempotent per `storeApi` instance — repeated
* calls (re-renders, StrictMode double-invoke, HMR remounts of the calling
* component) never re-install the global or re-import a bundle for a store
* that has already booted.
*/
export function bootPlugins(
bootPayload: Pick<BootPayload, "plugins">,
storeApi: StoreApi<AppState>,
feature: boolean,
): void {
if (!feature) return;
if (!bootPayload.plugins || bootPayload.plugins.length === 0) return;
if (bootedStores.has(storeApi)) return;
bootedStores.add(storeApi);
installPluginGlobal();
const theme = readResolvedTheme();
void loadPlugins(bootPayload.plugins, (pluginId) => buildHostApi(pluginId, storeApi, theme));
}