Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,61 @@ description and keep ownership on the side listed here.
under `~/.parsar/`; never use the repo checkout, container image working
directory, or the process CWD as hidden state.

### Plugin Bundle (KindBundle) architecture

- A Plugin Bundle is a `KindBundle` capability that packages server tools,
client UI, skills, and hooks as a single deployable unit installed via
`parsar plugin add`.
- **Server tools** run inside `server/plugin-host/` — a Node.js process
speaking MCP stdio protocol (JSON-RPC 2.0). The daemon spawns it like
any other MCP server (`{ command: "node", args: [...] }`).
- The plugin-host process is configured via `PARSAR_PLUGIN_HOST_PATH` (env
var pointing at `server/plugin-host/index.js`). When unset, bundles with
`server_entry` are silently skipped with a log warning.
- Plugin server code lives on disk at `<PARSAR_DATA_DIR>/plugins/<dir>/`.
The CLI copies files during `parsar plugin add`; the server reads them
at prompt time via the plugin-host `--plugins-dir` argument.
- Directory names strip the `@scope/` prefix from bundle names
(`@internal/hotel-ops` → `hotel-ops`). This logic is duplicated in
`apps/parsar/internal/cli/plugin.go` (`pluginDirName`) and
`server/internal/connector/agentdaemon/capability_runtime.go`
(`bundleNameToDirName`) — keep both in sync.
- `resolveBundleCapability` returns a `bundleResolution` struct containing
both system prompt injections (skills) and MCP server configs (tools).
The MCP server name is `"plugin:<bundle_name>"`.
- Plugin SDK (`server/plugin-host/lib/sdk.js`) provides
`ctx.tools.define(name, { description, parameters, handler })`. Future
phases will add `ctx.hooks`, `ctx.credentials`, and `ctx.api`.
- Plugin tool handlers have a 30-second timeout. Errors are returned as
MCP tool-level errors (`isError: true`), not JSON-RPC errors.
- **Client UI** uses a slot-based extension system
(`apps/web/src/lib/plugin-slots.ts`). Plugins register React components
to named slots via `ctx.slots.register(slotId, { key, component, match? })`.
- Slot types: `single` (last registration replaces), `list` (all render
in order), `chain` (first match wins — used for tool-card rendering).
- Client bundles are built by the CLI during `parsar plugin add` using
esbuild (`server/plugin-host/build-client.js`). Output goes to
`<plugins_dir>/<name>/dist/client.js`. Served via
`GET /api/v1/plugins/{name}/client.js`.
- React is shared via `window.__PARSAR_PLUGIN_API__` (exposed in
`plugin-init.ts`). Plugins must NOT bundle their own React.
- Plugin client bundles use IIFE format with a `require()` shim and an
esbuild `externalize-react` plugin. Standard `import React` works;
`react-dom` specific APIs (`createPortal`, etc.) are not yet supported.
- The frontend loads plugin clients on page load via `usePluginClients`
hook. Binding/unbinding a capability triggers an immediate reload
through React Query invalidation.
- Predefined slot IDs (add new ones as FDE needs arise):
`workspace.main`, `workspace.content`, `layout.header.actions`,
`layout.nav.bottom`, `conversation.tool-card`,
`conversation.header.actions`, `conversation.input.dock`,
`conversation.composer.left/right`, `agent.workspace`,
`agent.settings.section`.
- Adding a new slot point: wrap the target area with
`<SingleSlot slotId="..." fallback={<OriginalContent />} />` or insert
`<ListSlot slotId="..." />` at the desired position. Each new slot is
3–5 lines of code.

### Human interaction lifecycle

- `agent_interactions` is the canonical durable record for permission prompts
Expand Down
Loading
Loading