Files
paperclip-docs/docs/reference/api/plugins.md
T
631a6a0c53 Release docs for paperclip v2026.817.0 (content + screenshot overhaul) (#95)
Ships the v2026.817.0 docs to live: the doc content that was authored
and verified
earlier, plus a full screenshot overhaul.

The content half (`cdf81e2` and the nightly commits behind it) has
already been
reviewed. **The screenshots are the part that needs eyes** — `f24d734`,
342 PNGs.

## Screenshots

Everything was recaptured against the release commit
`213dabab4f8e1f3bb1803a2924c0fea1289fcd4c`
(`upstream/candidate/release-2026.817.0`). The previous shots were 375
parent-commits stale (v2026.720.0), with 228 of 310 entries flagged
pending. All
342 registry entries now carry that SHA.

> ⚠️ Worth knowing: the release commit is **not** an ancestor of the
parent's
> `master`, and local `master` was four days *older* than it. Capturing
against
> `master` would have both missed release UI and leaked 93 post-tag
commits' worth
> of UI into the release docs.

### New coverage (34 entries)

Three guides shipped with **no images at all** despite being 115–136
lines. They
now have them:

| Guide | Shots |
|---|---|
| `guides/day-to-day/decisions.md` | queue, an agent-proposed decision
with its options, triage strip, filter panel, named queue, Decided
curtain, empty state |
| `experimental/status-cards.md` | board, New card dialog, and all four
drawer tabs |
| `experimental/task-chat.md` | thread, composer mode picker, side pane
|
| `reference/api/secrets.md` | the board-side Proposals tab |

Chat-Style Tasks is captured in a **separate pass**:
`enableTaskChatRedesign` is
instance-wide, so leaving it on would have turned every classic
issue/task/work-mode
shot into the redesigned chat.

## Two defects that were already live

Both were shipping in the docs before this PR:

1. **`issues/detail-chat.png` and the `tasks/*` shots were mostly plugin
debug
output.** The Kitchen Sink example plugin mounts a panel into the issue
page
that dumps the raw issue JSON, and the seed installed it. Those guides
were
illustrating a debug panel instead of the task page. The seed no longer
   installs it.
2. **The seeded task thread was a monologue** — four board-user messages
in a row,
because the board actor cannot post agent comments. It is now a real
two-sided
   exchange.

## Targets repointed because the UI moved

| Target | Why |
|---|---|
| `activity/*` | `Activity.tsx` deleted; `/activity` renders
`pages/audit/CompanyActivity.tsx` |
| `experimental/cloud-upstream` | `CloudUpstream.tsx` deleted; the route
now redirects to `/company/export` |
| `settings/*`, `plugins/*`, `adapters/*` | `/instance/settings/*` are
legacy redirects now |
| `dashboard/stale-tasks-panel` | panel removed from `Dashboard.tsx` —
target dropped |

Plus eight targets with **silent selector drift**. A failed interaction
step falls
back to "whatever state was reached", so these had been quietly
producing wrong
images — a plain inbox where the doc promised a mode picker, a whole
dashboard
where it promised one panel. Full table in
`docs/user-guides/screenshots/SCREENSHOTS_PLAN_v2026.817.0.md` §C2.

## Pipeline

Decisions, status-card summaries and secret proposals are all
**agent-authored**,
and the board actor the seed runs as is refused by every one of them
(`"Agent run context required"`, `"Secret proposals require a verified
run-bound
agent token"`). Rather than insert rows behind the API,
`seed-new-surfaces.mjs`
borrows the two doors a real run already has — a board-minted agent API
key, and a
locally signed agent JWT — so every write goes through the production
code path.

Also:
- `capture.mjs` records a real `captured_against` on a detached checkout
instead of
the literal string `"HEAD"` (which is what every pre-2026.817 entry
recorded), and
  gains a `scrollTo` step for content below the fold.
- `run.mjs` gains `--serve` for iterating on the seed, the task-chat
phase, and a
keeper that holds the demo board's issue statuses steady — the demo
`process`
agents finish runs without declaring a disposition, so the stalled-work
sweep
re-blocks issues every few minutes over a capture that runs far longer.

## Reviewing

Light and dark both captured at 1440×900 @2x. The new surfaces are the
ones to
scrutinise; the rest is a like-for-like refresh.

### One leftover

Dropping `dashboard/stale-tasks-panel` leaves
`{light,dark}/dashboard/stale-tasks-panel.png`
tracked but unused — they need deleting by hand.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: DevRel Gemini <[email protected]>
Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Dotta <[email protected]>
Co-authored-by: ClaudeCoder <[email protected]>
2026-08-18 12:43:56 +02:00

9.1 KiB

paperclip_version
paperclip_version
v2026.529.0

Plugins API

The plugins API is the REST surface behind the Plugin Manager UI and the plugin runtime bridge. It covers installation, lifecycle (enable/disable/upgrade), config, jobs, webhooks, local-folder mounts, dashboard data, and the worker-side bridge calls plugins make back to the host.

For the operator-facing walkthrough see Administration → Plugins. For the authoring SDK see Reference → Plugins → Plugin SDK.

All routes are mounted under /api. Most are instance-admin operations; the company-scoped routes are explicit in the path.


Registry and discovery

Endpoint Purpose
GET /api/plugins List installed plugins on this instance.
GET /api/plugins/examples List the bundled plugins discovered in the current checkout (reference examples and vendored first-party plugins). See below.
GET /api/plugins/ui-contributions Aggregate of UI slots and launchers contributed by enabled plugins. The Paperclip UI consumes this on page load.
GET /api/plugins/tools Tools declared by enabled plugins.
POST /api/plugins/tools/execute Execute a plugin-declared tool by key.

Bundled plugin discovery

GET /api/plugins/examples returns the plugins that ship inside the current Paperclip checkout. Rather than a fixed list, the server scans the bundled packages on disk for plugin manifests, so the result reflects whatever your checkout actually contains — the reference example plugins plus any vendored first-party plugins. The result is cached after the first scan.

Each entry has this shape:

{
  "packageName": "@paperclipai/plugin-llm-wiki",
  "pluginKey": "llm-wiki",
  "displayName": "LLM Wiki",
  "description": "…",
  "localPath": "packages/plugins/…",
  "tag": "first-party",
  "experimental": true
}
  • tag is example for the reference plugins or first-party for vendored real plugins.
  • experimental is true for first-party plugins that aren't production-ready yet; the Plugin Manager renders these with an Experimental badge.
  • localPath is what the install flow points at for an in-checkout install.

Install and lifecycle

Endpoint Purpose
POST /api/plugins/install Install a plugin package.
GET /api/plugins/:pluginId Read the installed plugin record.
DELETE /api/plugins/:pluginId Uninstall the plugin.
POST /api/plugins/:pluginId/enable Enable the plugin; starts its worker.
POST /api/plugins/:pluginId/disable Disable the plugin; stops its worker and hides UI contributions.
POST /api/plugins/:pluginId/upgrade Run the upgrade flow against the plugin's source.
GET /api/plugins/:pluginId/health Read the current health diagnostics reported by the plugin worker.
GET /api/plugins/:pluginId/logs Read the plugin's recent worker logs.

Installing a plugin

POST /api/plugins/install handles two kinds of install through one route, and the request body is what tells Paperclip which one you mean. Either way it's an instance-admin operation, because the install flow fetches and inspects package contents on the host.

Field Type Purpose
packageName string Required. An npm package name for a registry install, or a filesystem path for a local install.
version string Optional. Target version for npm installs; defaults to latest.
isLocalPath boolean Optional. Set true when packageName is a filesystem path rather than an npm package name.

A successful install responds with the plugin record, the same shape GET /api/plugins/:pluginId returns.

For npm installs, a packageName containing any of < > : " | ? * is rejected with 400 and packageName contains invalid characters. That check is skipped for local paths, which need those characters on some platforms — local paths get the stricter treatment described next instead.

How local install paths are checked

When you set isLocalPath: true, Paperclip canonicalizes the packageName value before anything else touches it: it resolves the string to an absolute path, then to its real path — collapsing .. traversal segments and following every symlink — and requires the result to be an existing, readable directory. The loader only ever receives that canonical path, as its localPath install option, so two different spellings of the same folder can never reach different decisions.

This is worth knowing for two practical reasons. Symlinked plugin folders install as their real target, so that's the path Paperclip records and watches. And a path that doesn't exist yet fails immediately with a clear message instead of surfacing later as a confusing manifest error.

A path that fails canonicalization comes back as 400, with the reason in the error field:

error Meaning
Invalid localPath: path contains a null byte The submitted string contained a \0 byte.
Invalid localPath: path does not exist: <absolute path> Nothing is at the resolved path. Usually a typo, or a folder that hasn't been created yet.
Invalid localPath: path is not a directory: <canonical path> The path resolves to a file. Point at the package folder, not at a file inside it.
Invalid localPath: path is not readable: <canonical path> The directory exists but the server process can't read it.

Install sources on cloud-managed instances

If your instance is managed by a Paperclip control plane rather than run by you, plugin installation is narrowed to a positive allowlist: only plugins bundled with the application itself can be installed. Everything else is rejected with 403.

error When
npm installs are disabled on cloud-managed instances; only plugins bundled with the application may be installed The request had no isLocalPath: true, so it's a registry install.
cloud-managed instances may only install plugins from the bundled plugin catalog A local path was given, but its canonical form falls outside the bundled plugin catalog root (packages/plugins in the application bundle). The catalog root itself doesn't count — the source has to be a package directory inside it.

GET /api/plugins/examples is the practical way to see what's installable there — it scans that same catalog root, and each entry's localPath is a path the allowlist accepts. Self-hosted instances are unaffected: any readable directory on the server's filesystem is a valid local install source, and npm installs work normally. The canonicalization above still applies everywhere.

Config

Endpoint Purpose
GET /api/plugins/:pluginId/config Read the resolved instance config.
POST /api/plugins/:pluginId/config Write a new instance config.
POST /api/plugins/:pluginId/config/test Validate a candidate config without persisting it — invokes the plugin's validateConfig RPC.

Jobs and webhooks

Endpoint Purpose
GET /api/plugins/:pluginId/jobs List the plugin's declared jobs.
GET /api/plugins/:pluginId/jobs/:jobId/runs List recent runs for a job.
POST /api/plugins/:pluginId/jobs/:jobId/trigger Manually trigger a job run.
POST /api/plugins/:pluginId/webhooks/:endpointKey Inbound webhook delivery to the plugin worker's handleWebhook.

Local folders

Plugins can declare local-folder mounts; the UI and CLI use these endpoints to inspect and configure them per company.

Endpoint Purpose
GET /api/plugins/:pluginId/companies/:companyId/local-folders List configured local-folder mounts for the plugin/company pair.
GET /api/plugins/:pluginId/companies/:companyId/local-folders/:folderKey/status Status of one mount (configured / problems / listing summary).
POST /api/plugins/:pluginId/companies/:companyId/local-folders/:folderKey/validate Validate a candidate folder path without persisting.
PUT /api/plugins/:pluginId/companies/:companyId/local-folders/:folderKey Persist a folder configuration for that mount.

Dashboard

Endpoint Purpose
GET /api/plugins/:pluginId/dashboard Read the dashboard payload the plugin exposes to the UI.

Worker bridge

These endpoints back the worker-to-host bridge — the calls a plugin worker makes when it asks the host for data, performs an action, or opens a stream. They are typically consumed by the runtime itself, but they are valid REST endpoints for tests and replay.

Endpoint Purpose
POST /api/plugins/:pluginId/bridge/data Resolve a data request the worker initiated.
POST /api/plugins/:pluginId/bridge/action Resolve an action request the worker initiated.
GET /api/plugins/:pluginId/bridge/stream/:channel Open a stream channel from the worker.
POST /api/plugins/:pluginId/data/:key UI-facing entry to read a plugin's registered data feed by key.
POST /api/plugins/:pluginId/actions/:key UI-facing entry to invoke a plugin's registered action by key.

Stability. The plugin runtime ships in alpha. Endpoint shapes here may change between Paperclip releases — pin your plugin and Paperclip versions in lockstep.