Imported from BlackBeltTechnology/pi-agent-dashboard (
packages/dashboard-plugin-runtime/src/AGENTS.md). Install upstream withnpx skills add BlackBeltTechnology/pi-agent-dashboard --skill src. Copyright stays with the author.
DOX — packages/dashboard-plugin-runtime/src
Files in this directory. One row per source file.
| File | Purpose |
|---|---|
__tests__/ws-route-registry.test.ts |
Plugin WS route registry decision tables (test-plan #E1-#E3, spec plugin-ws-route): duplicate scope / nested prefix throw + first stays active + failed plugins marked in status store; 8 reserved cases throw + registry unchanged; late registration throws, toggle off→on re-registers afresh (new handler generation); teardown closes tracked sockets 1001. Drives the REAL loadServerEntries over committed fixture plugins in __tests__/fixtures/ws-route-plugins/packages/ (temp-dir entries can't be dynamically imported under vitest; fixture order pinned by manifest priority). See change: add-browser-relay. |
dependency-graph.ts |
Pure plugin-activation dependency graph. Exports GraphNode, Graph, buildGraph, transitiveDependents, transitiveDependencies, computeToggleImpact (returns cascadeEnable/cascadeDisable/blockers), detectCycles, topologicalSort (priority+id tier tiebreak, cyclic nodes appended last). Shared by server route handler + client cascade confirm dialog. |
index.ts |
Runtime barrel. Now also exports getSessionEvents (was internal). See change: add-goal-continuation-plugin. Exports SlotPill, SlotPillProps, SlotAccent. See change: redesign-directory-card. Exports the slot-claims invalidation store: bumpSlotClaimsVersion, getSlotClaimsVersion, subscribeSlotClaimsVersion, useSlotClaimsVersion, __resetSlotClaimsVersionForTests. See change: add-blackhole-session-pipeline. Adds usePluginConfigOf export. See change: model-picker-everywhere-favorites. Barrel re-exports ComposerContextGroupSlot + ComposerContextGroup. See change: move-quota-to-context-strip. |
folder-menu-contributions.tsx |
Folder-actions-menu contribution registry — the sibling bridge from a pill-grid slot section to the folder HEADER's menu (callbacks are section-local closures, so a prop cannot carry them). Exports FOLDER_MENU_GROUPS (workspace · directory · create · open · maintenance), FolderMenuGroup, isFolderMenuGroup, FolderMenuContribution ({id,group,label,icon,onSelect,badge?,disabled?} — no node, no pressed), RegisteredFolderMenuItem, isValidFolderMenuContribution (5 required fields + known group; else DROPPED), selectFolderMenuItems (sort pluginId then id; cross-plugin id collision → lower pluginId, load-order independent), createFolderMenuStore/FolderMenuStore, FolderMenuProvider (+ module default store when unmounted), useFolderMenuStore, useFolderMenuItem(scope, contribution), useFolderMenuRefresher(scope, refresh) (no item of its own), useFolderMenuItems(scope) (useSyncExternalStore, so an OPEN menu converges on a late registration), useFolderMenuRefreshRunner(). Store keyed (pluginId, id) — latest registration wins, dispose is identity-guarded so a superseded cleanup cannot evict the live entry; unmount deregisters. pluginId stamped from useCurrentPluginId(), never the payload. scope = folder cwd; null scope (e.g. placement === "card") registers nothing. See change: move-slot-actions-to-menu. |
intent-renderer.tsx |
Shell-side React component. Walks IntentNode tree, resolves primitive name via useUiPrimitiveOrNull, recursively renders nested IntentNode props/arrays. Exports isIntentNode, IntentActionSender, IntentRendererProps, UnknownPrimitive (dashed-red placeholder), IntentRenderer. wireActions converts ActionDescriptor map into send(action,payload) handlers. |
intent-store.ts |
Client store for plugin_intents broadcasts. Exports IntentKey, keyToString, IntentStoreEntry, IntentStore class (Map keyed `pluginId |
manifest-validator.ts |
Hand-rolled manifest validator. Throws ManifestValidationError with pluginId + reason. No Zod dep. Validates optional requires shape: piExtensions/binaries/services each must be string[] when present. See change: add-plugin-activation-ui. Validates shell-overlay-route presentation is "page"|"dialog" — FATAL on an unknown value (unlike depth, which warns and defaults) so a typo cannot silently restore the opted-out behaviour. See change: add-route-backed-overlay-dialogs. Requires a non-empty customType + component on custom-entry-renderer claims and rejects intra-plugin duplicate (custom-entry-renderer, customType) pairs beside the tool-renderer/command-route checks. See change: add-custom-entry-renderer-slot. |
plugin-action-bridge.ts |
Client→server plugin_action bridge. Exports PluginActionMessage, setSender(sender) (called by useWebSocket on connect), sendPluginAction(pluginId,sessionId,action,payload) (no-op when disconnected). Lives in runtime to dodge circular import with client package. |
plugin-context.tsx |
PluginContextProvider, CurrentPluginLayer, useCurrentPluginId() (plugin identity for registration APIs; null outside a layer), usePluginConfig<T>(), useAllSessions, useSessionState, usePluginLogger, usePluginSend, usePluginRouter, useSlotRegistry, applyPluginConfigUpdate. Per-plugin context layer scopes hooks to contributing plugin's id. Adds usePluginConfigOf<T>(pluginId) — reactive ANY-plugin config read; useSyncExternalStore over module store; no provider; never throws; frozen EMPTY_CONFIG snapshot for an unset id (a fresh {} would loop). Adds usePluginMessage<T>(type, handler) — read ONE server→browser message type off the shell WS (handler in a ref, malformed frame ignored, no-op without a socket); the read half of usePluginSend for plugins owning a global surface (browser relay status/frames). See changes: model-picker-everywhere-favorites, add-browser-relay. |
prompt-component-registry.ts |
PromptBus component registry. Built-in types generic-dialog (inline) + architect-prompt (widget-bar). Exports PromptComponentInfo, getPromptComponentInfo (falls back to generic-dialog), registerPromptComponent (idempotent under HMR), isWidgetBarPrompt, useHasWidgetBarPrompt(sessionId) reactive suppression hook. Moved from packages/client/src/lib/. |
session-data-store.ts |
Per-session client-side data store. Exports subscribeSessionDataKey(key, cb) — all-sessions-for-a-key subscription. Reset cleanup teardown. See change: add-flows-subcard. |
session-events-store.ts |
Module-level per-session DashboardEvent side-channel store for plugins. Exports publishSessionEvent, publishSessionEvents (batch, O(N) not O(N²)), clearSessionEvents, getSessionEvents, subscribeSessionEvents (stable frozen-array refs), __resetSessionEventsStoreForTests. Consumed by useSessionEvents hook in plugin-context.tsx. |
slot-claims-invalidation.ts |
Slot-claims invalidation store (design D3) — the re-render nudge for gates whose signal arrives after first render. Module-level version counter + listener set. Exports bumpSlotClaimsVersion() (version++ + fire listeners synchronously), getSlotClaimsVersion, subscribeSlotClaimsVersion, useSlotClaimsVersion() (useSyncExternalStore subscription; invoke BEFORE any registry-null early return), __resetSlotClaimsVersionForTests. Global signals only — no per-session payload rides a bump. Until a bump, behaviour identical to pre-change. See change: add-blackhole-session-pipeline. |
settings-draft-context.tsx |
Settings draft registry. SettingsDraftProvider + useSettingsDraftSource({id,page,isDirty,commit,reset}). Settings sections (built-in + plugin) buffer edits; host SettingsPanel commits dirty sources on unified Save. No-op without provider. See change: unify-settings-save-contract. |
shell-sessions-context.tsx |
React context exposing shell's ReadonlyMap<string,DashboardSession> to plugin claims. Exports ShellSessionsValue, ShellSessionsProvider, useShellSession(sessionId) (strict, throws outside provider), useShellSessionOrNull(sessionId) (soft). Used by <ShellOverlayRouteSlot> to resolve DashboardSession metadata by config.sessionParam. |
slot-consumers.tsx |
One component per slot id: SidebarFolderSectionSlot, SessionCardBadgeSlot, SessionCardActionBarSlot, ContentViewSlot, ContentHeaderStickySlot, ContentInlineFooterSlot, AnchoredPopoverSlot, CommandRouteSlot, SettingsSectionSlot, ToolRendererSlot. Each wraps contributions in SlotErrorBoundary. — Adds SessionCardMemorySlot + WorkspaceActionBarSlot consumers and useSlotHasClaimsForSession(slotId, session) hook for parent containers to conditionally render. See change: redesign-session-card-subcards. useSlotHasClaimsForSession + session-scoped slot consumers consult shouldRender via forSessionRendered. See change: auto-hide-empty-session-subcards. Adds SessionCardFlowsSlot({session}) mirroring SessionCardMemorySlot (legacy + intent). See change: add-flows-subcard. Adds SettingsSectionByPluginSlot({pluginId}) rendering settings-section claims scoped to a single plugin id; consumed by PluginSettingsHost. See change: add-plugin-activation-ui. ToolRendererSlot accepts + forwards expanded optional props (status, result, toolDetails, images, context) to claim Component and FallbackComponent. See change: wire-tool-renderer-slot. Adds WorktreeCardSectionSlot({folder}) mirroring SidebarFolderSectionSlot for the worktree-card-section slot; renders folder-scoped claims inside a worktree session card scoped to the worktree cwd. See change: kb-row-on-worktree-session-card. ShellOverlayRouteSlot renders the matched claim body inside a flex-1 min-h-0 relative height wrapper and nothing else — no dialog chrome of its own. Container choice belongs to the HOST: it reads the claim's effective presentation (default "dialog", "page" opts out) via the exported useShellOverlayRoutePresentation hook and lifts a dialog claim out of its content region, because the overlay underlay must cover the VIEWPORT and cannot be positioned from inside this slot. An earlier dialogContainer injection prop was removed for that reason (design D2a); a hook returning a string also avoids the client-utils → dashboard-plugin-runtime cycle a component import would close. See change: add-route-backed-overlay-dialogs. Adds ComposerPanelSlot({draft,language?,sessionId?,sessionStatus?,onApplyText}) — renders composer-panel claims below the composer input, forwarding the read-only draft context + a bounded onApplyText draft-write; null when unclaimed. See change: make-grammar-fully-plugin-contained. ComposerContextGroupSlot({session}) mirrors ContentInlineFooterSlot for composer-context-group (session-scoped, priority-ordered, shouldRender-aware); the exported ComposerContextGroup({label,children,testId?}) primitive renders a divider + uppercase label + children as one non-shrinking flex unit. See change: move-quota-to-context-strip. useSlotHasClaimsForSession, every forSessionRendered-evaluating session consumer, and ContentViewSlot (one-active predicate re-evaluation) subscribe to the slot-claims invalidation store via useSlotClaimsVersion() invoked BEFORE any registry-null early return — a late bumpSlotClaimsVersion() re-renders gates on idle sessions that never broadcast again. See change: add-blackhole-session-pipeline. |
SlotPill.tsx |
Shared single-concern directory-card slot pill. Exports SlotPill, SlotPillProps, SlotAccent, SlotSurface. Glyph + uppercase micro-label + bold count (children) + optional inline state; whole pill = one nav click target (role="button", Enter/Space) that stopPropagation. Static accent map (blue/indigo/cyan/teal/purple/red). surface: raised = --bg-secondary + shadow; flat = NO fill, NO shadow (border only) for session-card placement. Capsule legend fill follows surface via LEGEND_BG (--bg-tertiary raised / --bg-primary flat) so it masks the border line. Home per design D1 so all four Folder*Sections share one source. STATE-ONLY: actions?: ReactNode REMOVED — compile-time break, no runtime shim; slot actions are folder-menu-contributions.tsx items. Fact markers (KB ⚠ N stale) stay as children. See change: redesign-directory-card. See change: move-slot-actions-to-menu. |
slot-error-boundary.tsx |
Per-claim React error boundary. Logs [slot-error-boundary] Plugin "<id>" slot "<slot>" threw: + renders nothing for failing claim without suppressing siblings. |
slot-registry.ts |
createSlotRegistry() — typed Map<SlotId, ClaimEntry[]> pre-sorted by (priority, pluginId). Filter helpers: forSession, forFolder, forCommand, forToolName. ClaimEntry.shouldRender?: (props) => boolean plus forSessionRendered(claims, session) apply predicate AND shouldRender. See change: auto-hide-empty-session-subcards. ClaimEntry<S extends SlotId> generic; predicate?/shouldRender? method-shorthand (bivariant). See change: slot-generic-claim-entry. Adds setEnabledSet(ids: Set<string>) + internal enabled-filter in getClaims/getAllClaims so disabled plugins contribute zero claims at runtime. Adds getAllPluginsForActivationUi() returning every registered plugin (enabled or not) for PluginsSection list. Driven from client by usePluginEnabledSet. See change: add-plugin-activation-ui. Adds isPluginEnabled(id) (true until the first setEnabledSet, else set membership) so consumers can gate NON-claim contributions — the internal filter covers claims only. forTab DELETED with its last caller: settings-section claims no longer route by tab. See change: reorganize-settings-into-pages, plugin-settings-pages. Adds forCustomType(claims, customType) (EXACT match) beside forToolName; ClaimEntry gains customType?. See change: add-custom-entry-renderer-slot. |
ui-primitive-context.tsx |
React context provider + lookup hooks for UI primitives. Exports UiPrimitiveProvider, useUiPrimitive<K>(key) (strict, throws if unregistered or outside provider), useUiPrimitiveOrNull<K>(key) (soft, null fallback). Both @deprecated for direct plugin use — plugins should emit server-side intents; hooks remain canonical resolver for shell's IntentRenderer. |
ui-primitive-registry.ts |
Runtime data structure for UI primitives. Exports UiPrimitiveRegistry (opaque, private _impls map), createUiPrimitiveRegistry(), registerUiPrimitive(reg,key,impl) (throws on double-registration, first-write-wins), getUiPrimitiveImpl (internal lookup, returns undefined if missing). Pairs with ui-primitive-context.tsx. |