Skip to content

Feature/plugin bundle phase2 - #261

Open
liyangbing wants to merge 3 commits into
mainfrom
feature/plugin-bundle-phase2
Open

Feature/plugin bundle phase2#261
liyangbing wants to merge 3 commits into
mainfrom
feature/plugin-bundle-phase2

Conversation

@liyangbing

Copy link
Copy Markdown
Collaborator

What & why
Plugin Phase 2: Client UI. Plugins can now register React components to named "slots" in the web UI, replacing or extending any section — from custom tool-result cards to full-page workspace takeover. This lets FDEs build complete business interfaces (hotel dashboard, e-commerce workbench) on top of Parsar without forking the frontend.

How
Slot registry (plugin-slots.ts): A version-counting singleton with three slot modes (single/list/chain). Components subscribe via useSyncExternalStore using the version number as snapshot — avoids the referential-instability trap of returning arrays directly.

Plugin loading: usePluginClients queries the capabilities API for bundles, fetches /api/v1/plugins/{name}/client.js, and executes via new Function(). On capability unbind, unloadPlugin() removes the slot registrations and the UI falls back to defaults immediately (React Query invalidation, no polling).

Build pipeline: CLI plugin add detects manifest.client.entry, invokes node build-client.js which runs esbuild in IIFE mode with an externalize-react plugin. React is shared from the host via window.PARSAR_PLUGIN_API — plugins never bundle their own React.

Trade-offs:

new Function() over dynamic import() — works without ESM module-serving infrastructure; blocked by strict CSP but enterprise private deployments don't typically enforce it.
React shared via window global over import-maps — simpler, no browser version constraints, same approach DSH uses (window.ModuleLoader).
Slot points are added incrementally (3-5 lines each) rather than making everything a plugin — pragmatic for an existing product vs. DSH's "everything is a plugin from day one" approach.
Also bumps Go 1.25.12 → 1.25.13 to resolve 7 stdlib vulnerabilities flagged by govulncheck.

Verification
make check-go (all Go tests pass)
make check-web (tsc typecheck + design lint)
make check-cli (CLI + opencode-plugin typecheck)
make check-hygiene
Manual smoke: installed hotel-ops with client → full-screen workspace renders (KPI cards, room grid, events panel). Unbinding the capability removes the workspace and restores default UI without page refresh.
Notes for reviewers
react-dom shim returns the React namespace — plugins cannot use createPortal/flushSync yet. Acceptable for Phase 2; will need a separate ReactDOM bridge if a future plugin requires portals.
extractPresentationFromSteps parses __parsar_presentation client-side from tool step results. No server-side extraction needed because the daemon already forwards MCP content blocks through the event pipeline.
Two pre-existing test failures (TestTruncate in CLI, TestInstallPlugins_ConcurrentSamePluginNoTruncation in claudecode) are unrelated to this PR — they reproduce on the base branch with both Go versions.
The workspace.main slot in App.tsx replaces everything including navigation. If only the chat area should be replaced, plugins should register workspace.content instead. Both coexist — plugin authors choose the scope they need.

Introduce a new capability kind 'bundle' that packages skills (and in
later phases: server tools, client UI, hooks) as a single deployable
plugin unit. This is Phase 0 of the Plugin platform architecture.

Changes:
- canonical: add KindBundle constant, BundleSpec struct with inline
  skills, Validate/UnmarshalJSON branches, ErrInvalidBundle sentinel
- render: all 4 targets (ClaudeCode/OpenCode/Codex/Pi) support KindBundle
- agentdaemon: resolveBundleCapability extracts inline skills and injects
  them as append-mode system prompts
- store: normalizeCapabilityType and validateImportSpecPreCommit accept
  'bundle'
- dev routes: POST .../capabilities/plugins/install endpoint for CLI
- dev routes: isListedCapabilityType includes 'bundle' so bundles appear
  in workspace capability listings
- CLI: 'parsar plugin add/list/remove' subcommands that read a local
  plugin directory manifest, embed skill content, and call the server API
- frontend: add Plugin tab to capability type filter, CapabilityTypeBadge
  handles 'bundle' type
- examples: customer-service-skin demo plugin (skill-only, changes Agent
  persona to professional customer service style)

Verified end-to-end: install plugin via API -> bind to Agent -> new
conversation shows customer-service persona in responses (system prompt
injection confirmed in server logs).
Plugin bundles with a server_entry now get their tools exposed as an
MCP server that the daemon spawns on demand.

Architecture:
- server/plugin-host/: Node.js MCP server (JSON-RPC 2.0 stdio)
  loads plugin server modules and exposes ctx.tools.define API
- resolveBundleCapability emits {command:'node', args:[...]} when
  ServerEntry is non-empty and PARSAR_PLUGIN_HOST_PATH is configured
- CLI 'parsar plugin add' copies server files to ~/.parsar/plugins/
- CLI 'parsar plugin remove' cleans up on-disk files

New env var: PARSAR_PLUGIN_HOST_PATH (absolute path to plugin-host/index.js)

Example: examples/plugins/hotel-ops/ with check_room_status and
suggest_pricing tools, validated end-to-end.

Also fixes Phase 0 web typecheck errors (missing i18n key for bundle
type, MarketplaceTab type narrowing).
Introduces the client-side plugin system: plugins can register React
components to named slots in the web UI, replacing or extending any
section of the interface.

Architecture:
- Slot registry (plugin-slots.ts): single/list/chain slot types with
  version-counting useSyncExternalStore integration
- SlotRenderer components: ToolCardSlot, ListSlot, SingleSlot with
  PluginErrorBoundary isolation
- Plugin loader: fetches /api/v1/plugins/{name}/client.js, executes
  via new Function(), idempotent load/unload on capability changes
- window.__PARSAR_PLUGIN_API__ exposes shared React + definePlugin API
- CLI auto-builds client TSX with esbuild during plugin add
- Go endpoint serves built client.js from plugins directory

Slot extension points placed in:
- App.tsx: workspace.main (full-page takeover)
- ConversationsPage: workspace.content, conversation.header.actions,
  conversation.input.dock, conversation.tool-card
- AdminLayout: layout.header.actions, layout.nav.bottom

Example: hotel-ops plugin extended with full workspace UI (KPI cards,
room grid, events panel) + custom tool-result cards (RoomStatusCard,
PricingCard).

Also bumps Go 1.25.12 → 1.25.13 to fix 7 stdlib vulnerabilities
(GO-2026-6218 through GO-2026-5026).
@liyangbing
liyangbing requested a review from RyanLee-Dev August 24, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant