Feature/plugin bundle phase1 - #260
Open
liyangbing wants to merge 2 commits into
Open
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Plugin Phase 1: Server Tools. Bundles that declare a server_entry now get their tools exposed as an MCP server the daemon spawns on demand. This is the core value of the plugin system — agents can call custom business tools defined by FDEs, without touching engine code or writing full MCP servers by hand.
Builds on Phase 0 (KindBundle + Skill injection, merged in feature/plugin-bundle-phase0).
How
Plugin Host (server/plugin-host/): A lightweight Node.js process speaking MCP stdio protocol (JSON-RPC 2.0). On startup it scans --plugins-dir for the named plugin, loads its
index.js
, and calls the default export with a PluginServerContext that provides ctx.tools.define(). All registered tools are then available via tools/list and tools/call.
Go resolver: resolveBundleCapability now returns a bundleResolution struct. When ServerEntry is non-empty and PARSAR_PLUGIN_HOST_PATH is configured, it emits a standard {command: "node", args: [...]} MCP server entry — the daemon handles it identically to any other MCP capability. No new spawn logic needed.
CLI: parsar plugin add copies plugin files to ~/.parsar/plugins// before the API call (if copy fails, no orphan DB record). parsar plugin remove cleans up the disk directory after API deletion.
Trade-offs:
One plugin-host process per bundle per prompt (not a shared long-lived process). Simple and stateless — matches how MCP servers work today. Phase 2 may revisit if FDEs need shared state across tools.
Directory name strips @scope/ prefix — two differently-scoped plugins with the same base name would collide. Acceptable for single-workspace enterprise deployment; documented as known constraint.
Verification
make check-go (sqlc drift + all Go unit tests)
make check-web (tsc typecheck + design lint)
make check-cli (CLI + opencode-plugin typechecks)
make check-hygiene
make check-store (requires Postgres; no schema changes in this PR)
Manual smoke: installed hotel-ops plugin → bound to agent → asked "please hotel price" → agent called suggest_pricing → returned occupancy-based pricing (screenshot in thread)
Notes for reviewers
bundleNameToDirName (Go) and pluginDirName (CLI) are intentionally duplicated with cross-reference comments. A shared package felt premature for 4 lines of logic.
server/plugin-host/ has no node_modules — it uses only Node.js built-ins (no external dependencies).
The PARSAR_PLUGIN_HOST_PATH env var is opt-in. When unset, bundles with server_entry are silently skipped with a warning log. Existing deployments are unaffected.
Phase 0 left two web typecheck errors (missing i18n key, MarketplaceTab type mismatch) — fixed here as drive-by.