mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 22:24:16 +01:00
feat(plugins): load image-owned catalogs and persistent app overlays
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
64895f187b
commit
be9c775d9e
17 files changed
+470
-2
No files matched your search
@@ -0,0 +1,85 @@
|
||||
# Plugins supplied by an application distribution
|
||||
|
||||
A downstream image can add prebuilt plugins without changing Paperclip's
|
||||
built-in catalog. The operator owns the image and trusts its plugin code.
|
||||
This is packaging and activation policy, not a sandbox or entitlement system.
|
||||
Ordinary self-hosted images need no catalog and keep their existing behavior.
|
||||
|
||||
## Image layout
|
||||
|
||||
Use `distribution/catalog.json` beneath `PAPERCLIP_BUNDLED_PLUGIN_ROOT`
|
||||
(default `/app/packages/plugins`). Each plugin has a stable directory below
|
||||
`distribution/`, containing its `package.json`, compiled manifest, worker and
|
||||
optional UI. Bundle runtime dependencies; startup never installs them.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"plugins": [{
|
||||
"key": "example-extension",
|
||||
"pluginKey": "example.extension",
|
||||
"version": "1.0.0",
|
||||
"directory": "example-extension",
|
||||
"digest": "sha256:<64 lowercase hex characters>"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
The digest covers a sorted depth-first file inventory. Each entry is
|
||||
`[relativePosixPath, "sha256:" + sha256(fileBytes), permissionBits & 0777]`.
|
||||
Hash the UTF-8 JSON serialization of the inventory and prefix it with
|
||||
`sha256:`. The server's `distributionBundleDigest` implements this contract.
|
||||
There are no symbolic links or special files. A bundle is limited to 10,000
|
||||
files, 256 MiB and 32 directory levels. Catalog keys, plugin IDs and directory
|
||||
names must be unique; a distribution cannot replace a built-in key or ID.
|
||||
|
||||
On startup, the host validates the catalog, hashes the bundle before importing
|
||||
its executable manifest, and validates package version and confined prebuilt
|
||||
entrypoints. Malformed catalogs and integrity failures stop startup. Deploy
|
||||
the catalog and bundles atomically as part of the image; keep them read-only
|
||||
in operation. This detects packaging errors but does not authenticate an
|
||||
untrusted image builder. Image provenance and signatures remain deployment
|
||||
responsibilities.
|
||||
|
||||
## Selection, upgrades and rollback
|
||||
|
||||
Managed instances select a distribution key through the existing
|
||||
`plugins.autoInstall` list. Existing install, capability validation, API
|
||||
compatibility, worker and health mechanisms apply. A worker or install failure
|
||||
is recorded as a plugin error without taking down the application.
|
||||
|
||||
The catalog alone does not auto-enable plugins on self-hosted instances.
|
||||
Operators can explicitly install catalog entries through the normal plugin
|
||||
CLI. The package's manifest ID and version must match the catalog.
|
||||
|
||||
Keep each key's directory stable across releases. The activation guard also
|
||||
covers persisted installs: a plugin removed from the image catalog, or no
|
||||
longer selected in managed configuration, cannot activate on restart. Its
|
||||
database records remain for rollback. Plugin data migrations must themselves
|
||||
support the intended rollback window; removing a bundle does not undo them.
|
||||
|
||||
A deployment controller must generate `plugins.autoInstall` from the **target
|
||||
image's** catalog. A union of catalogs from different releases is insufficient:
|
||||
an older image rejects a key it does not know. Before reverting to a host
|
||||
version that predates this catalog contract, disable the distribution plugins
|
||||
and remove their keys from configuration. Such older hosts do not have the
|
||||
new activation guard.
|
||||
|
||||
## Persistent application UI
|
||||
|
||||
The `appShellOverlay` slot requires `ui.action.register`. It receives the usual
|
||||
`PluginWidgetProps` context. It mounts once in both application shells and
|
||||
survives route navigation. It is disposed when the account or selected company
|
||||
changes, during onboarding, and on sign-out. It is not mounted on login pages.
|
||||
|
||||
The host positions contributions above the mobile navigation and stacks them
|
||||
at the bottom right. Each plugin owns its launcher, panel, keyboard handling,
|
||||
focus restoration, accessible labels and request cancellation. Use a bounded,
|
||||
responsive panel. This slot is not a launcher placement zone and does not
|
||||
replace modal/launcher APIs. Errors remain inside the existing plugin mount
|
||||
error boundary.
|
||||
|
||||
UI code is trusted browser code. Host context is display context, never proof
|
||||
of server authorization. A distribution backend must independently validate
|
||||
the signed-in session and enforce company, tenant and user access rules for
|
||||
every read and mutation. Keep provider secrets out of plugin UI and manifests.
|
||||
@@ -368,6 +368,7 @@ Mount surfaces currently wired in the host include:
|
||||
- `taskDetailView`
|
||||
- `projectSidebarItem`
|
||||
- `globalToolbarButton`
|
||||
- `appShellOverlay` (persistent, signed-in application shell)
|
||||
- `toolbarButton`
|
||||
- `contextMenuItem`
|
||||
- `commentAnnotation`
|
||||
@@ -613,3 +614,6 @@ pnpm -r typecheck
|
||||
pnpm test:run
|
||||
pnpm build
|
||||
```
|
||||
|
||||
For image-supplied plugins and the persistent shell lifecycle, see
|
||||
[Distribution plugins](DISTRIBUTION-PLUGINS.md).
|
||||
@@ -360,6 +360,7 @@ export interface PaperclipPluginManifestV1 {
|
||||
| "sidebarPanel"
|
||||
| "projectSidebarItem"
|
||||
| "globalToolbarButton"
|
||||
| "appShellOverlay"
|
||||
| "toolbarButton"
|
||||
| "contextMenuItem"
|
||||
| "commentAnnotation"
|
||||
|
||||
Reference in new issue
Block a user