@blackbelt-technology/dashboard-plugin-runtime

中文文档
0.11.0发布于 2026/10/26MITTypeScriptES Module构建来源已验证近 30 天下载 1941

Plugin loader, slot registry, slot consumers, and context API for pi-dashboard

原始使用文档
文档目录14 节
  1. Import paths
  2. Secure-context helpers
  3. Minimum manifest
  4. Slot claims
  5. settings-section placement
  6. settings-section nav hint
  7. Client-side PluginContext API
  8. Slot-claims invalidation store
  9. Server-side ServerPluginContext API
  10. Bridge auto-register
  11. Plugin config persistence
  12. Failure isolation rules
  13. Demo plugin
  14. Spawn-env contributors (experimental)

@blackbelt-technology/dashboard-plugin-runtime

Plugin loader, slot registry, slot consumers, plugin context API, and Vite plugin for pi-dashboard.

Import paths

PathContents
@blackbelt-technology/dashboard-plugin-runtimeSlot consumers, registry types, barrel
@blackbelt-technology/dashboard-plugin-runtime/contextDEPRECATED alias (one minor): import hooks (usePluginConfig, useAllSessions, etc.) from the root, shell-only names from ./host
@blackbelt-technology/dashboard-plugin-runtime/serverLoader, ServerPluginContext, config validator
@blackbelt-technology/dashboard-plugin-runtime/vite-pluginviteDashboardPluginsPlugin

Secure-context helpers

randomUuid() and copyText(text) (barrel export) work on plain-http LAN origins, where crypto.randomUUID and navigator.clipboard are undefined. Plugins MUST use them instead of the bare APIs (enforced by a scan test). First carried by runtime version 0.9.1 (the release after 0.9.0); a plugin adopting them must raise its runtime floor to that release. See change: audit-plugin-bundles-non-secure-context.

Plugins MUST only import from these public paths. Importing from packages/client/, packages/server/, or any other internal package is banned and will fail the lint suite.

Minimum manifest

Add a pi-dashboard-plugin field to your package's package.json:

{
  "name": "@blackbelt-technology/my-feature-plugin",
  "pi-dashboard-plugin": {
    "id": "my-feature",
    "displayName": "My Feature",
    "priority": 100,
    "client": "./dist/client/index.js",
    "server": "./dist/server/index.js",
    "claims": [
      { "slot": "session-card-badge", "component": "MyFeatureBadge" }
    ]
  }
}

Fields:

  • id — globally unique kebab-case id.
  • priority — sort order for multi-contribution slots. Lower = rendered first. Default 1000; first-party = 100.
  • client — path to the built client entry (exporting React components by name).
  • server — optional path to the server entry (exports default function registerPlugin(ctx: ServerPluginContext)).
  • bridge — optional path to a pi-extension/bridge entry (auto-registered into ~/.pi/agent/settings.json under dashboard-<id>).
  • configSchema — optional relative path to a JSON Schema 7 file for plugin config validation.
  • claims — array of slot claims.

Slot claims

Each claim targets one slot:

{ "slot": "session-card-badge", "component": "MyBadge" }
{ "slot": "tool-renderer", "toolName": "MyTool", "component": "MyToolRenderer" }
{ "slot": "command-route", "command": "/myfeature", "component": "MyFeatureView" }
{ "slot": "anchored-popover", "trigger": "my-trigger-button", "component": "MyPopover" }
{ "slot": "settings-section", "component": "MySettings", "tab": "general" }

settings-section placement

Every settings-section claim renders on the owning plugin's own page, /settings/plugins/<id>, beneath host-owned chrome. That page is listed under the Plugins nav entry while the plugin is enabled.

tab is inert: accepted for backwards compatibility (any string, no warning), read by nothing. It does not pick a Settings page.

settings-section nav hint

A claim may ask the host to PROMOTE the plugin's page into a settings nav group:

{
  "slot": "settings-section",
  "component": "BuiltInRolesSettings",
  "nav": { "group": "models", "label": "Model roles", "description": "Pick which model answers each @role.", "order": 1000 }
}
FieldRule
grouprequired, non-empty after trim. Only models is honoured; unknown values are accepted and ignored (forward-compat).
labelrequired, non-empty, ≤ 40 chars after NFKC + trim. Rail label + page title.
descriptionoptional, ≤ 200 chars. Page lede. Blank → dropped alone.
orderoptional finite number, default 1000. Sort key inside the group (independent of manifest priority).
  • Placement only: the URL stays /settings/plugins/<id>; no capability is granted.
  • Invalid hint (non-object, missing/blank field, over-length, any Unicode Cc/Cf character, non-finite order) → dropped with one warning naming plugin id, claim index, field. Never fatal: the plugin loads normally. nav on any other slot is dropped silently.
  • Honoured only for a first-party plugin — npm package in the @blackbelt-technology/ scope (firstParty on GET /api/plugins rows; not priority).
  • A label colliding with any built-in settings page or group label (English + every shipped locale, NFKC + case-fold) is ignored; between promoted plugins, the lower (order, id) wins.
  • A promoted page gets a compact host chrome (label title, description lede, "Provided by the plugin" + toggle, pill only when unhealthy, metadata behind a disclosure). A disabled promoted plugin stays listed in its group, marked "off".
  • Strings render as plain text, never markup.

See change: promote-model-roles-settings.

Client-side PluginContext API

Plugin client components receive props from the slot consumer. Hooks are available via the nearest PluginContextProvider:

import {
  usePluginConfig,
  useAllSessions,
  useSessionState,
  usePluginLogger,
  usePluginSend,
  usePluginRouter,
} from "@blackbelt-technology/dashboard-plugin-runtime";

function MyBadge({ session }) {
  const config = usePluginConfig<{ enabled: boolean }>();
  const logger = usePluginLogger(); // logs as [plugin:my-feature]
  const send = usePluginSend();
  // ...
}

usePluginConfig<T>() is reactive — it re-renders when POST /api/config/plugins/<id> succeeds and the server broadcasts plugin_config_update.

You MUST call these hooks from within a slot contribution component (i.e. inside a CurrentPluginLayer). Calling them from outside throws a descriptive error.

Slot-claims invalidation store

A plugin whose shouldRender gate depends on a signal that resolves AFTER first render (e.g. a boot-time installed check) calls bumpSlotClaimsVersion() once the signal lands. Every mounted gate wrapper — including cards of idle/ended sessions that never broadcast session_updated again — re-invokes shouldRender synchronously. Global signals only; no per-session payload rides a bump. See change: add-blackhole-session-pipeline.

import { bumpSlotClaimsVersion } from "@blackbelt-technology/dashboard-plugin-runtime";

Server-side ServerPluginContext API

Your server entry must export a default registerPlugin function:

// packages/my-feature-plugin/server/index.ts
import type { ServerPluginContext } from "@blackbelt-technology/dashboard-plugin-runtime/server";

export default async function registerPlugin(ctx: ServerPluginContext): Promise<void> {
  ctx.fastify.get("/api/my-feature/status", async () => {
    return { ok: true };
  });

  ctx.logger.info("my-feature plugin loaded");
}

Available on ctx:

  • fastify — Fastify instance for REST routes.
  • sessionManager / eventStore — read-only dashboard state.
  • broadcastToSubscribers(msg) — send a WebSocket message to all subscribed browsers.
  • registerPiHandler(type, handler) / registerBrowserHandler(type, handler) — hook into WebSocket message flows.
  • getPluginConfig<T>() — read this plugin's config from ~/.pi/dashboard/config.json#plugins.<id>.*.
  • updatePluginConfig<T>(partial) — validate, merge, persist, and broadcast plugin_config_update.
  • isPiExtensionInstalled?(name) — boolean-only installed-check against pi's package registry (union of global+local scopes, ~30s cached). Optional: absent on older hosts / injected test contexts — the plugin owns a fallback. A scan failure REJECTS (never resolves false), so a false is authoritative. See change: add-blackhole-session-pipeline.
  • logger — namespaced logger ([plugin:<id>]).

Bridge auto-register

If your manifest declares bridge, the dashboard auto-registers it in ~/.pi/agent/settings.json under dashboardPluginBridges["dashboard-<id>"] on server startup. The dashboard removes the entry when the plugin is disabled.

The dashboard- key prefix is reserved. User-owned extension entries in packages[] are never touched.

Plugin config persistence

Plugin settings live at ~/.pi/dashboard/config.json#plugins.<id>.*.

{
  "plugins": {
    "my-feature": { "enabled": true, "pollInterval": 30 }
  }
}

If your manifest declares configSchema, the loader:

  • Applies schema default values on read.
  • Validates writes before persisting (rejects with ValidationError on schema violation).

Failure isolation rules

  • A plugin throwing during manifest validation, server-side load, or client-side render does NOT crash the dashboard.
  • Failures are reflected in /api/health.plugins[] as { loaded: false, error: "..." }.
  • Slot consumer error boundaries catch React render errors per-claim (not per-slot), so one plugin crashing does not suppress siblings.

Demo plugin

packages/demo-plugin/ is a private fixture package that exercises the runtime end-to-end. It is excluded from production builds (manifest declares fixture: true). Do not use it as a template for real plugins.

Spawn-env contributors (experimental)

Trusted plugins (priority <= 100, same gate as spawnSession) may call ctx.registerSpawnEnvContributor(fn, opts?) on the server context. fn({ mechanism }) synchronously returns env vars added to every dashboard-spawned pi session (headless, tmux, wt, wsl-tmux). The host never overrides an inherited variable, rejects reserved names (PATH, HOME, NODE_*, LD_*, DYLD_*, PI_DASHBOARD_*, ELECTRON_*, CONTEXT_MODE_BRIDGE_*), skips throwing contributors, and skips a plugin while it is disabled. opts.supersede = { marker, names } lets the host delete listed names (only those declared) before applying contributions. Policy, not a sandbox. See packages/context-mode-settings-plugin for the reference user.

版本与发布记录

@blackbelt-technology/dashboard-plugin-runtime 共发布 22 个版本,首次发布于 2026/04/30;过去 12 个月发布了 22 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.11.0latest2026/10/2613 天前
0.10.02026/10/2019 天前
0.9.02026/10/061 个月前
0.8.02026/08/262 个月前
0.7.02026/07/243 个月前
0.6.12026/07/213 个月前

其他发布通道:next → 0.0.0-test-darwin-x64.3。

关于 @blackbelt-technology/dashboard-plugin-runtime 的常见问题

@blackbelt-technology/dashboard-plugin-runtime 是什么?

Plugin loader, slot registry, slot consumers, and context API for pi-dashboard

如何安装 @blackbelt-technology/dashboard-plugin-runtime?

在项目目录运行 npm install @blackbelt-technology/dashboard-plugin-runtime,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vite(^6.0.0)、react(>=18.0.0)、wouter(^3.9.0)、fastify(^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@blackbelt-technology/dashboard-plugin-runtime 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@blackbelt-technology/dashboard-plugin-runtime 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@blackbelt-technology/dashboard-plugin-runtime 可以商用吗?

@blackbelt-technology/dashboard-plugin-runtime 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@blackbelt-technology/dashboard-plugin-runtime 的下载量有多少?

根据 npm 官方下载统计,@blackbelt-technology/dashboard-plugin-runtime 在 2026/10/09 – 2026/11/07 这 30 天内被下载 1,941 次,日均约 65 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@blackbelt-technology/dashboard-plugin-runtime 还在维护吗?

最新版本 0.11.0 发布于 2026/10/26(13 天前);至今共发布 22 个版本,其中过去 12 个月发布了 22 个;首次发布于 2026/04/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。