Files
kandev/apps/web/lib/plugins/active-plugin.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

33 lines
1.1 KiB
TypeScript

/**
* Builds the `ActivePlugin` shape (docs/plans/plugins/PLUGIN-API.md)
* for a plugin the operator just enabled, so the settings page can trigger a
* runtime `loadPlugins` call without a full page reload. Mirrors the
* backend's boot-payload mapping in
* apps/backend/internal/backendapp/helpers.go (bundleActivePlugins /
* pluginStyleURLs) — keep both in sync.
*/
import type { ActivePlugin } from "./types";
import type { PluginRecord } from "@/lib/types/plugins";
/**
* Returns the `ActivePlugin` for a plugin record, or null when the plugin
* declares no UI bundle (nothing to load).
*/
export function toActivePlugin(record: PluginRecord): ActivePlugin | null {
const bundle = record.ui?.bundle;
if (!bundle) return null;
return {
id: record.id,
name: record.display_name,
bundleUrl: `/api/plugins/${record.id}/bundle`,
styleUrls: pluginStyleURLs(record),
};
}
function pluginStyleURLs(record: PluginRecord): string[] | undefined {
const styles = record.ui?.styles;
if (!styles || styles.length === 0) return undefined;
return styles.map((style) => `/api/plugins/${record.id}/ui${style}`);
}