diff --git a/doc/plugins/PLUGIN_SPEC.md b/doc/plugins/PLUGIN_SPEC.md new file mode 100644 index 00000000..5c3d3993 --- /dev/null +++ b/doc/plugins/PLUGIN_SPEC.md @@ -0,0 +1,1140 @@ +# Paperclip Plugin System Specification + +Status: proposed complete spec for the post-V1 plugin system + +This document is the complete specification for Paperclip's plugin and extension architecture. +It expands the brief plugin notes in [doc/SPEC.md](../SPEC.md) and should be read alongside the comparative analysis in [doc/plugins/ideas-from-opencode.md](./ideas-from-opencode.md). + +This is not part of the V1 implementation contract in [doc/SPEC-implementation.md](../SPEC-implementation.md). +It is the full target architecture for the plugin system that should follow V1. + +## 1. Scope + +This spec covers: + +- plugin packaging and installation +- runtime model +- trust model +- capability system +- UI extension surfaces +- event, job, and webhook surfaces +- workspace-oriented extension surfaces +- Postgres persistence for extensions +- operator workflows +- compatibility and upgrade rules + +This spec does not cover: + +- a public marketplace +- cloud/SaaS multi-tenancy +- arbitrary third-party schema migrations in the first plugin version +- arbitrary frontend bundle injection in the first plugin version + +## 2. Core Assumptions + +Paperclip plugin design is based on the following assumptions: + +1. Paperclip is single-tenant and self-hosted. +2. Plugin installation is global to the instance. +3. "Companies" remain core Paperclip business objects, but they are not plugin trust boundaries. +4. Board governance, approval gates, budget hard-stops, and core task invariants remain owned by Paperclip core. +5. Projects already have a real workspace model via `project_workspaces`, and local/runtime plugins should build on that instead of inventing a separate workspace abstraction. + +## 3. Goals + +The plugin system must: + +1. Let operators install global instance-wide plugins. +2. Let plugins add major capabilities without editing Paperclip core. +3. Keep core governance and auditing intact. +4. Support both local/runtime plugins and external SaaS connectors. +5. Support future plugin categories such as: + - new agent adapters + - revenue tracking + - knowledge base + - issue tracker sync + - metrics/dashboards + - file/project tooling +6. Use simple, explicit, typed contracts. +7. Keep failures isolated so one plugin does not crash the entire instance. + +## 4. Non-Goals + +The first plugin system must not: + +1. Allow arbitrary plugins to override core routes or core invariants. +2. Allow arbitrary plugins to mutate approval, auth, issue checkout, or budget enforcement logic. +3. Allow arbitrary third-party plugins to run free-form DB migrations. +4. Depend on project-local plugin folders such as `.paperclip/plugins`. +5. Depend on automatic install-and-execute behavior at server startup from arbitrary config files. + +## 5. Terminology + +### 5.1 Instance + +The single Paperclip deployment an operator installs and controls. + +### 5.2 Company + +A first-class Paperclip business object inside the instance. + +### 5.3 Project Workspace + +A workspace attached to a project through `project_workspaces`. +This is the primary local runtime anchor for file, terminal, git, and process tooling. + +### 5.4 Platform Module + +A trusted in-process extension loaded directly by Paperclip core. + +Examples: + +- agent adapters +- storage providers +- secret providers +- run-log backends + +### 5.5 Plugin + +An installable instance-wide extension package loaded through the Paperclip plugin runtime. + +Examples: + +- Linear sync +- GitHub Issues sync +- Grafana widgets +- Stripe revenue sync +- file browser +- terminal +- git workflow + +### 5.6 Plugin Worker + +The runtime process used for a plugin. +In this spec, third-party plugins run out-of-process by default. + +### 5.7 Capability + +A named permission the host grants to a plugin. +Plugins may only call host APIs that are covered by granted capabilities. + +## 6. Extension Classes + +Paperclip has two extension classes. + +## 6.1 Platform Modules + +Platform modules are: + +- trusted +- in-process +- host-integrated +- low-level + +They use explicit registries, not the general plugin worker protocol. + +Platform module surfaces: + +- `registerAgentAdapter()` +- `registerStorageProvider()` +- `registerSecretProvider()` +- `registerRunLogStore()` +- future `registerWorkspaceRuntime()` if needed + +Platform modules are the right place for: + +- new agent adapter packages +- new storage backends +- new secret backends +- other host-internal systems that need direct process or DB integration + +## 6.2 Plugins + +Plugins are: + +- globally installed per instance +- loaded through the plugin runtime +- additive +- capability-gated +- isolated from core via a stable SDK and host protocol + +Plugin categories: + +- `connector` +- `workspace` +- `automation` +- `ui` + +A plugin may declare more than one category. + +## 7. Project Workspaces Are The Local Tooling Anchor + +Paperclip already has a concrete workspace model: + +- projects expose `workspaces` +- projects expose `primaryWorkspace` +- the database contains `project_workspaces` +- project routes already manage workspaces +- heartbeat resolution already prefers project workspaces before falling back to task-session or agent-home workspaces + +Therefore: + +1. File plugins should browse project workspaces first. +2. Terminal sessions should launch against project workspaces by default. +3. Git plugins should treat the selected project workspace as the repo root anchor. +4. Process/server tracking should attach to project workspaces whenever possible. +5. Issue and agent views may deep-link into project workspace context. + +Project workspaces may exist in two modes: + +- local directory mode: `cwd` is present +- repo-only mode: `repoUrl` and optional `repoRef` exist, but there is no local `cwd` + +Plugins must handle repo-only workspaces explicitly: + +- they may show metadata +- they may show sync state +- they may not assume local file/PTY/git access until a real `cwd` exists + +## 8. Installation Model + +Plugin installation is global and operator-driven. + +There is no per-company install table and no per-company enable/disable switch. + +If a plugin needs business-object-specific mappings, those are stored as plugin configuration or plugin state. + +Examples: + +- one global Linear plugin install +- mappings from company A to Linear team X and company B to Linear team Y +- one global git plugin install +- per-project workspace state stored under `project_workspace` + +## 8.1 On-Disk Layout + +Plugins live under the Paperclip instance directory. + +Suggested layout: + +- `~/.paperclip/instances/default/plugins/package.json` +- `~/.paperclip/instances/default/plugins/node_modules/` +- `~/.paperclip/instances/default/plugins/.cache/` +- `~/.paperclip/instances/default/data/plugins//` + +The package install directory and the plugin data directory are separate. + +## 8.2 Operator Commands + +Paperclip should add CLI commands: + +- `pnpm paperclipai plugin list` +- `pnpm paperclipai plugin install ` +- `pnpm paperclipai plugin uninstall ` +- `pnpm paperclipai plugin upgrade [version]` +- `pnpm paperclipai plugin doctor ` + +These commands are instance-level operations. + +## 8.3 Install Process + +The install process is: + +1. Resolve npm package and version. +2. Install into the instance plugin directory. +3. Read and validate plugin manifest. +4. Reject incompatible plugin API versions. +5. Display requested capabilities to the operator. +6. Persist install record in Postgres. +7. Start plugin worker and run health/validation. +8. Mark plugin `ready` or `error`. + +## 9. Load Order And Precedence + +Load order must be deterministic. + +1. core platform modules +2. built-in first-party plugins +3. installed plugins sorted by: + - explicit operator-configured order if present + - otherwise manifest `id` + +Rules: + +- plugin contributions are additive by default +- plugins may not override core routes or core actions by name collision +- if two plugins contribute the same route slug or UI slot id, the host must reject startup or force the operator to resolve the collision explicitly + +## 10. Package Contract + +Each plugin package must export a manifest and a worker entrypoint. + +Suggested package layout: + +- `dist/manifest.js` +- `dist/worker.js` + +Suggested `package.json` keys: + +```json +{ + "name": "@paperclip/plugin-linear", + "version": "0.1.0", + "paperclipPlugin": { + "manifest": "./dist/manifest.js", + "worker": "./dist/worker.js" + } +} +``` + +## 10.1 Manifest Shape + +Normative manifest shape: + +```ts +export interface PaperclipPluginManifestV1 { + id: string; + apiVersion: 1; + version: string; + displayName: string; + description: string; + categories: Array<"connector" | "workspace" | "automation" | "ui">; + minimumPaperclipVersion?: string; + capabilities: string[]; + entrypoints: { + worker: string; + }; + instanceConfigSchema?: JsonSchema; + jobs?: PluginJobDeclaration[]; + webhooks?: PluginWebhookDeclaration[]; + ui?: PluginUiDeclaration; +} +``` + +Rules: + +- `id` must be globally unique +- `id` should normally equal the npm package name +- `apiVersion` must match the host-supported plugin API version +- `capabilities` must be static and install-time visible +- config schema must be JSON Schema compatible + +## 11. Runtime Model + +## 11.1 Process Model + +Third-party plugins run out-of-process by default. + +Default runtime: + +- Paperclip server starts one worker process per installed plugin +- the worker process is a Node process +- host and worker communicate over JSON-RPC on stdio + +This design provides: + +- failure isolation +- clearer logging boundaries +- easier resource limits +- a cleaner trust boundary than arbitrary in-process execution + +## 11.2 Host Responsibilities + +The host is responsible for: + +- package install +- manifest validation +- capability enforcement +- process supervision +- job scheduling +- webhook routing +- activity log writes +- secret resolution +- workspace service enforcement +- UI route registration + +## 11.3 Worker Responsibilities + +The plugin worker is responsible for: + +- validating its own config +- handling domain events +- handling scheduled jobs +- handling webhooks +- producing UI view models +- invoking host services through the SDK +- reporting health information + +## 11.4 Failure Policy + +If a worker fails: + +- mark plugin status `error` +- surface error in plugin health UI +- keep the rest of the instance running +- retry start with bounded backoff +- do not drop other plugins or core services + +## 12. Host-Worker Protocol + +The host must support the following worker RPC methods. + +Required methods: + +- `initialize(input)` +- `health()` +- `shutdown()` + +Optional methods: + +- `validateConfig(input)` +- `onEvent(input)` +- `runJob(input)` +- `handleWebhook(input)` +- `getPageModel(input)` +- `getWidgetModel(input)` +- `getDetailTabModel(input)` +- `performAction(input)` + +### 12.1 `initialize` + +Called once on worker startup. + +Input includes: + +- plugin manifest +- resolved plugin config +- instance info +- host API version + +### 12.2 `health` + +Returns: + +- status +- current error if any +- optional plugin-reported diagnostics + +### 12.3 `validateConfig` + +Runs after config changes and startup. + +Returns: + +- `ok` +- warnings +- errors + +### 12.4 `onEvent` + +Receives one typed Paperclip domain event. + +Delivery semantics: + +- at least once +- plugin must be idempotent +- no global ordering guarantee across all event types +- per-entity ordering is best effort but not guaranteed after retries + +### 12.5 `runJob` + +Runs a declared scheduled job. + +The host provides: + +- job key +- trigger source +- run id +- schedule metadata + +### 12.6 `handleWebhook` + +Receives inbound webhook payload routed by the host. + +The host provides: + +- endpoint key +- headers +- raw body +- parsed body if applicable +- request id + +### 12.7 `getPageModel` + +Returns a schema-driven view model for the plugin's main page. + +### 12.8 `getWidgetModel` + +Returns a schema-driven view model for a dashboard widget. + +### 12.9 `getDetailTabModel` + +Returns a schema-driven view model for a project, issue, agent, goal, or run detail tab. + +### 12.10 `performAction` + +Runs an explicit plugin action initiated by the board UI. + +Examples: + +- "resync now" +- "link GitHub issue" +- "create branch from issue" +- "restart process" + +## 13. SDK Surface + +Plugins do not talk to the DB directly. +Plugins do not read raw secret material from persisted config. +Plugins do not touch the filesystem directly outside the host services. + +The SDK exposed to workers must provide typed host clients. + +Required SDK clients: + +- `ctx.config` +- `ctx.events` +- `ctx.jobs` +- `ctx.http` +- `ctx.secrets` +- `ctx.assets` +- `ctx.activity` +- `ctx.state` +- `ctx.entities` +- `ctx.projects` +- `ctx.issues` +- `ctx.agents` +- `ctx.goals` +- `ctx.workspace` +- `ctx.logger` + +## 13.1 Example SDK Shape + +```ts +export interface PluginContext { + manifest: PaperclipPluginManifestV1; + config: { + get(): Promise>; + }; + events: { + on(name: string, fn: (event: unknown) => Promise): void; + }; + jobs: { + register(key: string, input: { cron: string }, fn: (job: PluginJobContext) => Promise): void; + }; + state: { + get(input: ScopeKey): Promise; + set(input: ScopeKey, value: unknown): Promise; + delete(input: ScopeKey): Promise; + }; + entities: { + upsert(input: PluginEntityUpsert): Promise; + list(input: PluginEntityQuery): Promise; + }; + workspace: WorkspacePluginApi; +} +``` + +## 14. Capability Model + +Capabilities are mandatory and static. +Every plugin declares them up front. + +The host enforces capabilities in the SDK layer and refuses calls outside the granted set. + +## 14.1 Capability Categories + +### Data Read + +- `companies.read` +- `projects.read` +- `project.workspaces.read` +- `issues.read` +- `issue.comments.read` +- `agents.read` +- `goals.read` +- `activity.read` +- `costs.read` + +### Data Write + +- `issues.create` +- `issues.update` +- `issue.comments.create` +- `assets.write` +- `assets.read` +- `activity.log.write` +- `metrics.write` + +### Runtime / Integration + +- `events.subscribe` +- `jobs.schedule` +- `webhooks.receive` +- `http.outbound` +- `secrets.read-ref` + +### UI + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.detailTab.register` +- `ui.dashboardWidget.register` +- `ui.action.register` + +### Workspace + +- `workspace.fs.read` +- `workspace.fs.write` +- `workspace.fs.stat` +- `workspace.fs.search` +- `workspace.pty.open` +- `workspace.pty.input` +- `workspace.pty.resize` +- `workspace.pty.terminate` +- `workspace.pty.subscribe` +- `workspace.git.status` +- `workspace.git.diff` +- `workspace.git.log` +- `workspace.git.branch.create` +- `workspace.git.commit` +- `workspace.git.worktree.create` +- `workspace.git.push` +- `workspace.process.register` +- `workspace.process.list` +- `workspace.process.read` +- `workspace.process.terminate` +- `workspace.process.restart` +- `workspace.process.logs.read` +- `workspace.http.probe` + +## 14.2 Forbidden Capabilities + +The host must not expose capabilities for: + +- approval decisions +- budget override +- auth bypass +- issue checkout lock override +- direct DB access +- direct filesystem access outside approved workspace services + +## 14.3 Upgrade Rules + +If a plugin upgrade adds capabilities: + +1. the host must mark the plugin `upgrade_pending` +2. the operator must explicitly approve the new capability set +3. the new version does not become `ready` until approval completes + +## 15. Event System + +The host must emit typed domain events that plugins may subscribe to. + +Minimum event set: + +- `company.created` +- `company.updated` +- `project.created` +- `project.updated` +- `project.workspace_created` +- `project.workspace_updated` +- `project.workspace_deleted` +- `issue.created` +- `issue.updated` +- `issue.comment.created` +- `agent.created` +- `agent.updated` +- `agent.status_changed` +- `agent.run.started` +- `agent.run.finished` +- `agent.run.failed` +- `agent.run.cancelled` +- `approval.created` +- `approval.decided` +- `cost_event.created` +- `activity.logged` + +Each event must include: + +- event id +- event type +- occurred at +- actor metadata when applicable +- primary entity metadata +- typed payload + +## 16. Scheduled Jobs + +Plugins may declare scheduled jobs in their manifest. + +Job rules: + +1. Each job has a stable `job_key`. +2. The host is the scheduler of record. +3. The host prevents overlapping execution of the same plugin/job combination unless explicitly allowed later. +4. Every job run is recorded in Postgres. +5. Failed jobs are retryable. + +## 17. Webhooks + +Plugins may declare webhook endpoints in their manifest. + +Webhook route shape: + +- `POST /api/plugins/:pluginId/webhooks/:endpointKey` + +Rules: + +1. The host owns the public route. +2. The worker receives the request body through `handleWebhook`. +3. Signature verification happens in plugin code using secret refs resolved by the host. +4. Every delivery is recorded. +5. Webhook handling must be idempotent. + +## 18. UI Extension Model + +The first plugin UI system is schema-driven. + +The host renders plugin data using built-in UI components. +Plugins return view models, not arbitrary React bundles. + +## 18.1 Global Operator Routes + +- `/settings/plugins` +- `/settings/plugins/:pluginId` + +These routes are instance-level. + +## 18.2 Company-Context Routes + +- `/:companyPrefix/plugins/:pluginId` + +These routes exist because the board UI is organized around companies even though plugin installation is global. + +## 18.3 Detail Tabs + +Plugins may add tabs to: + +- project detail +- issue detail +- agent detail +- goal detail +- run detail + +Recommended route pattern: + +- `/:companyPrefix//:id?tab=` + +## 18.4 Dashboard Widgets + +Plugins may add cards or sections to the dashboard. + +## 18.5 Sidebar Entries + +Plugins may add sidebar links to: + +- global plugin settings +- company-context plugin pages + +## 18.6 Allowed View Model Types + +The host should support a limited set of schema-rendered components: + +- metric cards +- status lists +- tables +- timeseries charts +- markdown text +- key/value blocks +- action bars +- log views +- JSON/debug views + +Arbitrary frontend bundle injection is explicitly out of scope for the first plugin system. + +## 19. Workspace Service APIs + +Workspace service APIs are the foundation for local tooling plugins. + +All workspace APIs must route through the host and validate against known project workspace roots. + +## 19.1 Project Workspace APIs + +Required host APIs: + +- list project workspaces +- get project primary workspace +- resolve project workspace from issue +- resolve current workspace from agent/run when available + +## 19.2 File APIs + +- read file +- write file +- stat path +- search path or filename +- list directory + +All file APIs take a resolved workspace anchor plus a relative path. + +## 19.3 PTY APIs + +- open terminal session +- send input +- resize +- terminate +- subscribe to output + +PTY sessions should default to the selected project workspace when one exists. + +## 19.4 Git APIs + +- status +- diff +- log +- branch create +- worktree create +- commit +- push + +Git APIs require a local `cwd`. +If the workspace is repo-only, the host must reject local git operations until a local checkout exists. + +## 19.5 Process APIs + +- register process +- list processes +- read process metadata +- terminate +- restart +- read logs +- probe health endpoint + +Process tracking should attach to `project_workspace` when possible. + +## 20. Persistence And Postgres + +## 20.1 Database Principles + +1. Core Paperclip data stays in first-party tables. +2. Most plugin-owned data starts in generic extension tables. +3. Plugin data should scope to existing Paperclip objects before new tables are introduced. +4. Arbitrary third-party schema migrations are out of scope for the first plugin system. + +## 20.2 Core Table Reuse + +If data becomes part of the actual Paperclip product model, it should become a first-party table. + +Examples: + +- `project_workspaces` is already first-party +- if Paperclip later decides git state is core product data, it should become a first-party table too + +## 20.3 Required Tables + +### `plugins` + +- `id` uuid pk +- `plugin_key` text unique not null +- `package_name` text not null +- `version` text not null +- `api_version` int not null +- `categories` text[] not null +- `manifest_json` jsonb not null +- `status` enum: `installed | ready | error | upgrade_pending` +- `install_order` int null +- `installed_at` timestamptz not null +- `updated_at` timestamptz not null +- `last_error` text null + +Indexes: + +- unique `plugin_key` +- `status` + +### `plugin_config` + +- `id` uuid pk +- `plugin_id` uuid fk `plugins.id` unique not null +- `config_json` jsonb not null +- `installed_at` timestamptz not null +- `updated_at` timestamptz not null +- `last_error` text null + +### `plugin_state` + +- `id` uuid pk +- `plugin_id` uuid fk `plugins.id` not null +- `scope_kind` enum: `instance | company | project | project_workspace | agent | issue | goal | run` +- `scope_id` uuid/text null +- `namespace` text not null +- `state_key` text not null +- `value_json` jsonb not null +- `updated_at` timestamptz not null + +Constraints: + +- unique `(plugin_id, scope_kind, scope_id, namespace, state_key)` + +Examples: + +- Linear external IDs keyed by `issue` +- GitHub sync cursors keyed by `project` +- file browser preferences keyed by `project_workspace` +- git branch metadata keyed by `project_workspace` +- process metadata keyed by `project_workspace` or `run` + +### `plugin_jobs` + +- `id` uuid pk +- `plugin_id` uuid fk `plugins.id` not null +- `scope_kind` enum nullable +- `scope_id` uuid/text null +- `job_key` text not null +- `schedule` text null +- `status` enum: `idle | queued | running | error` +- `next_run_at` timestamptz null +- `last_started_at` timestamptz null +- `last_finished_at` timestamptz null +- `last_succeeded_at` timestamptz null +- `last_error` text null + +Constraints: + +- unique `(plugin_id, scope_kind, scope_id, job_key)` + +### `plugin_job_runs` + +- `id` uuid pk +- `plugin_job_id` uuid fk `plugin_jobs.id` not null +- `plugin_id` uuid fk `plugins.id` not null +- `status` enum: `queued | running | succeeded | failed | cancelled` +- `trigger` enum: `schedule | manual | retry` +- `started_at` timestamptz null +- `finished_at` timestamptz null +- `error` text null +- `details_json` jsonb null + +Indexes: + +- `(plugin_id, started_at desc)` +- `(plugin_job_id, started_at desc)` + +### `plugin_webhook_deliveries` + +- `id` uuid pk +- `plugin_id` uuid fk `plugins.id` not null +- `scope_kind` enum nullable +- `scope_id` uuid/text null +- `endpoint_key` text not null +- `status` enum: `received | processed | failed | ignored` +- `request_id` text null +- `headers_json` jsonb null +- `body_json` jsonb null +- `received_at` timestamptz not null +- `handled_at` timestamptz null +- `response_code` int null +- `error` text null + +Indexes: + +- `(plugin_id, received_at desc)` +- `(plugin_id, endpoint_key, received_at desc)` + +### `plugin_entities` (optional but recommended) + +- `id` uuid pk +- `plugin_id` uuid fk `plugins.id` not null +- `entity_type` text not null +- `scope_kind` enum not null +- `scope_id` uuid/text null +- `external_id` text null +- `title` text null +- `status` text null +- `data_json` jsonb not null +- `created_at` timestamptz not null +- `updated_at` timestamptz not null + +Indexes: + +- `(plugin_id, entity_type, external_id)` unique when `external_id` is not null +- `(plugin_id, scope_kind, scope_id, entity_type)` + +Use cases: + +- imported Linear issues +- imported GitHub issues +- plugin-owned process records +- plugin-owned external metric bindings + +## 20.4 Activity Log Changes + +The activity log should extend `actor_type` to include `plugin`. + +New actor enum: + +- `agent` +- `user` +- `system` +- `plugin` + +Plugin-originated mutations should write: + +- `actor_type = plugin` +- `actor_id = ` + +## 20.5 Plugin Migrations + +The first plugin system does not allow arbitrary third-party migrations. + +Later, if custom tables become necessary, the system may add a trusted-module-only migration path. + +## 21. Secrets + +Plugin config must never persist raw secret values. + +Rules: + +1. Plugin config stores secret refs only. +2. Secret refs resolve through the existing Paperclip secret provider system. +3. Plugin workers receive resolved secrets only at execution time. +4. Secret values must never be written to: + - plugin config JSON + - activity logs + - webhook delivery rows + - error messages + +## 22. Auditing + +All plugin-originated mutating actions must be auditable. + +Minimum requirements: + +- activity log entry for every mutation +- job run history +- webhook delivery history +- plugin health page +- install/upgrade history in `plugins` + +## 23. Operator UX + +## 23.1 Global Settings + +Global plugin settings page must show: + +- installed plugins +- versions +- status +- requested capabilities +- current errors +- install/upgrade/remove actions + +## 23.2 Plugin Settings Page + +Each plugin may expose: + +- config form derived from `instanceConfigSchema` +- health details +- recent job history +- recent webhook history +- capability list + +Route: + +- `/settings/plugins/:pluginId` + +## 23.3 Company-Context Plugin Page + +Each plugin may expose a company-context main page: + +- `/:companyPrefix/plugins/:pluginId` + +This page is where board users do most day-to-day work. + +## 24. Example Mappings + +This spec directly supports the following plugin types: + +- `@paperclip/plugin-workspace-files` +- `@paperclip/plugin-terminal` +- `@paperclip/plugin-git` +- `@paperclip/plugin-linear` +- `@paperclip/plugin-github-issues` +- `@paperclip/plugin-grafana` +- `@paperclip/plugin-runtime-processes` +- `@paperclip/plugin-stripe` + +## 25. Compatibility And Versioning + +Rules: + +1. Host supports one or more explicit plugin API versions. +2. Plugin manifest declares exactly one `apiVersion`. +3. Host rejects unsupported versions at install time. +4. SDK packages are versioned with the host protocol. +5. Plugin upgrades are explicit operator actions. +6. Capability expansion requires explicit operator approval. + +## 26. Recommended Delivery Order + +## Phase 1 + +- plugin manifest +- install/list/remove/upgrade CLI +- global settings UI +- plugin process manager +- capability enforcement +- `plugins`, `plugin_config`, `plugin_state`, `plugin_jobs`, `plugin_job_runs`, `plugin_webhook_deliveries` +- event bus +- jobs +- webhooks +- settings page +- dashboard widget/page/tab schema rendering + +This phase is enough for: + +- Linear +- GitHub Issues +- Grafana +- Stripe + +## Phase 2 + +- project workspace service built on `project_workspaces` +- file APIs +- PTY APIs +- git APIs +- process APIs +- project-context tabs for plugin pages + +This phase is enough for: + +- file browser +- terminal +- git workflow +- process/server tracking + +## Phase 3 + +- optional `plugin_entities` +- richer action systems +- trusted-module migration path if truly needed +- optional richer frontend extension model +- plugin ecosystem/distribution work + +## 27. Final Design Decision + +Paperclip should not implement a generic in-process hook bag modeled directly after local coding tools. + +Paperclip should implement: + +- trusted platform modules for low-level host integration +- globally installed out-of-process plugins for additive instance-wide capabilities +- schema-driven UI contributions +- project workspace-based local tooling +- generic extension tables for most plugin state +- strict preservation of core governance and audit rules + +That is the complete target design for the Paperclip plugin system. diff --git a/doc/plugins/ideas-from-opencode.md b/doc/plugins/ideas-from-opencode.md new file mode 100644 index 00000000..ac4077f7 --- /dev/null +++ b/doc/plugins/ideas-from-opencode.md @@ -0,0 +1,1637 @@ +# Plugin Ideas From OpenCode + +Status: design report, not a V1 commitment + +Paperclip V1 explicitly excludes a plugin framework in [doc/SPEC-implementation.md](../SPEC-implementation.md), but the long-horizon spec says the architecture should leave room for extensions. This report studies the `opencode` plugin system and translates the useful patterns into a Paperclip-shaped design. + +Assumption for this document: Paperclip is a single-tenant operator-controlled instance. Plugin installation should therefore be global across the instance. "Companies" are still first-class Paperclip objects, but they are organizational records, not tenant-isolation boundaries for plugin trust or installation. + +## Executive Summary + +`opencode` has a real plugin system already. It is intentionally low-friction: + +- plugins are plain JS/TS modules +- they load from local directories and npm packages +- they can hook many runtime events +- they can add custom tools +- they can extend provider auth flows +- they run in-process and can mutate runtime behavior directly + +That model works well for a local coding tool. It should not be copied literally into Paperclip. + +The main conclusion is: + +- Paperclip should copy `opencode`'s typed SDK, deterministic loading, low authoring friction, and clear extension surfaces. +- Paperclip should not copy `opencode`'s trust model, project-local plugin loading, "override by name collision" behavior, or arbitrary in-process mutation hooks for core business logic. +- Paperclip should use multiple extension classes instead of one generic plugin bag: + - trusted in-process modules for low-level platform concerns like agent adapters, storage providers, secret providers, and possibly run-log backends + - out-of-process plugins for most third-party integrations like Linear, GitHub Issues, Grafana, Stripe, and schedulers + - schema-driven UI contributions for dashboard widgets, settings panels, and company pages + - a typed event bus plus scheduled jobs for automation + +If Paperclip does this well, the examples you listed become straightforward: + +- file browser / terminal / git workflow / child process tracking become workspace-runtime plugins built on first-party primitives +- Linear / GitHub / Grafana / Stripe become connector plugins +- future knowledge base and accounting features can also fit the same model + +## Sources Examined + +I cloned `anomalyco/opencode` and reviewed commit: + +- `a965a062595403a8e0083e85770315d5dc9628ab` + +Primary files reviewed: + +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/plugin/src/index.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/plugin/src/tool.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/opencode/src/plugin/index.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/opencode/src/config/config.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/opencode/src/tool/registry.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/opencode/src/provider/auth.ts` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/web/src/content/docs/plugins.mdx` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/web/src/content/docs/custom-tools.mdx` +- `https://github.com/anomalyco/opencode/blob/a965a062595403a8e0083e85770315d5dc9628ab/packages/web/src/content/docs/ecosystem.mdx` + +Relevant Paperclip files reviewed for current extension seams: + +- [server/src/adapters/registry.ts](../../server/src/adapters/registry.ts) +- [ui/src/adapters/registry.ts](../../ui/src/adapters/registry.ts) +- [server/src/storage/provider-registry.ts](../../server/src/storage/provider-registry.ts) +- [server/src/secrets/provider-registry.ts](../../server/src/secrets/provider-registry.ts) +- [server/src/services/run-log-store.ts](../../server/src/services/run-log-store.ts) +- [server/src/services/activity-log.ts](../../server/src/services/activity-log.ts) +- [doc/SPEC.md](../SPEC.md) +- [doc/SPEC-implementation.md](../SPEC-implementation.md) + +## What OpenCode Actually Implements + +## 1. Plugin authoring API + +`opencode` exposes a small package, `@opencode-ai/plugin`, with a typed `Plugin` function and a typed `tool()` helper. + +Core shape: + +- a plugin is an async function that receives a context object +- the plugin returns a `Hooks` object +- hooks are optional +- plugins can also contribute tools and auth providers + +The plugin init context includes: + +- an SDK client +- current project info +- current directory +- current git worktree +- server URL +- Bun shell access + +That is important: `opencode` gives plugins rich runtime power immediately, not a narrow capability API. + +## 2. Hook model + +The hook set is broad. It includes: + +- event subscription +- config-time hook +- message hooks +- model parameter/header hooks +- permission decision hooks +- shell env injection +- tool execution before/after hooks +- tool definition mutation +- compaction prompt customization +- text completion transforms + +The implementation pattern is very simple: + +- core code constructs an `output` object +- each matching plugin hook runs sequentially +- hooks mutate the `output` +- final mutated output is used by core + +This is elegant and easy to extend. + +It is also extremely powerful. A plugin can change auth headers, model params, permission answers, tool inputs, tool descriptions, and shell environment. + +## 3. Plugin discovery and load order + +`opencode` supports two plugin sources: + +- local files +- npm packages + +Local directories: + +- `~/.config/opencode/plugins/` +- `.opencode/plugins/` + +Npm plugins: + +- listed in config under `plugin: []` + +Load order is deterministic and documented: + +1. global config +2. project config +3. global plugin directory +4. project plugin directory + +Important details: + +- config arrays are concatenated rather than replaced +- duplicate plugin names are deduplicated with higher-precedence entries winning +- internal first-party plugins and default plugins are also loaded through the plugin pipeline + +This gives `opencode` a real precedence model rather than "whatever loaded last by accident." + +## 4. Dependency handling + +For local config/plugin directories, `opencode` will: + +- ensure a `package.json` exists +- inject `@opencode-ai/plugin` +- run `bun install` + +That lets local plugins and local custom tools import dependencies. + +This is excellent for local developer ergonomics. + +It is not a safe default for an operator-controlled control plane server. + +## 5. Error handling + +Plugin load failures do not hard-crash the runtime by default. + +Instead, `opencode`: + +- logs the error +- publishes a session error event +- continues loading other plugins + +That is a good operational pattern. One bad plugin should not brick the entire product unless the operator has explicitly configured it as required. + +## 6. Tools are a first-class extension point + +`opencode` has two ways to add tools: + +- export tools directly from a plugin via `hook.tool` +- define local files in `.opencode/tools/` or global tools directories + +The tool API is strong: + +- tools have descriptions +- tools have Zod schemas +- tool execution gets context like session ID, message ID, directory, and worktree +- tools are merged into the same registry as built-in tools +- tool definitions themselves can be mutated by a `tool.definition` hook + +The most aggressive part of the design: + +- custom tools can override built-in tools by name + +That is very powerful for a local coding assistant. +It is too dangerous for Paperclip core actions. + +## 7. Auth is also a plugin surface + +`opencode` allows plugins to register auth methods for providers. + +A plugin can contribute: + +- auth method metadata +- prompt flows +- OAuth flows +- API key flows +- request loaders that adapt provider behavior after auth succeeds + +This is a strong pattern worth copying. Integrations often need custom auth UX and token handling. + +## 8. Ecosystem evidence + +The ecosystem page is the best proof that the model is working in practice. +Community plugins already cover: + +- sandbox/workspace systems +- auth providers +- session headers / telemetry +- memory/context features +- scheduling +- notifications +- worktree helpers +- background agents +- monitoring + +That validates the main thesis: a simple typed plugin API can create real ecosystem velocity. + +## What OpenCode Gets Right + +## 1. Separate plugin SDK from host runtime + +This is one of the best parts of the design. + +- plugin authors code against a clean public package +- host internals can evolve behind the loader +- runtime code and plugin code have a clean contract boundary + +Paperclip should absolutely do this. + +## 2. Deterministic loading and precedence + +`opencode` is explicit about: + +- where plugins come from +- how config merges +- what order wins + +Paperclip should copy this discipline. + +## 3. Low-ceremony authoring + +A plugin author does not have to learn a giant framework. + +- export async function +- return hooks +- optionally export tools + +That simplicity matters. + +## 4. Typed tool definitions + +The `tool()` helper is excellent: + +- typed +- schema-based +- easy to document +- easy for runtime validation + +Paperclip should adopt this style for plugin actions, automations, and UI schemas. + +## 5. Built-in features and plugins use similar shapes + +`opencode` uses the same hook system for internal and external plugin-style behavior in several places. +That reduces special cases. + +Paperclip can benefit from that with adapters, secret backends, storage providers, and connector modules. + +## 6. Incremental extension, not giant abstraction upfront + +`opencode` did not design a giant marketplace platform first. +It added concrete extension points that real features needed. + +That is the correct mindset for Paperclip too. + +## What Paperclip Should Not Copy Directly + +## 1. In-process arbitrary plugin code as the default + +`opencode` is basically a local agent runtime, so unsandboxed plugin execution is acceptable for its audience. + +Paperclip is a control plane for an operator-managed instance with company objects. +The risk profile is different: + +- secrets matter +- approval gates matter +- budgets matter +- mutating actions require auditability + +Default third-party plugins should not run with unrestricted in-process access to server memory, DB handles, and secrets. + +## 2. Project-local plugin loading + +`opencode` has project-local plugin folders because the tool is centered around a codebase. + +Paperclip is not project-scoped. It is instance-scoped. +The comparable unit is: + +- instance-installed plugin package + +Paperclip should not auto-load arbitrary code from a workspace repo like `.paperclip/plugins` or project directories. + +## 3. Arbitrary mutation hooks on core business decisions + +Hooks like: + +- `permission.ask` +- `tool.execute.before` +- `chat.headers` +- `shell.env` + +make sense in `opencode`. + +For Paperclip, equivalent hooks into: + +- approval decisions +- issue checkout semantics +- activity log behavior +- budget enforcement + +would be a mistake. + +Core invariants should stay in core code, not become hook-rewritable. + +## 4. Override-by-name collision + +Allowing a plugin to replace a built-in tool by name is useful in a local agent product. + +Paperclip should not allow plugins to silently replace: + +- core routes +- core mutating actions +- auth behaviors +- permission evaluators +- budget logic +- audit logic + +Extension should be additive or explicitly delegated, never accidental shadowing. + +## 5. Auto-install and execute from user config + +`opencode`'s "install dependencies at startup" flow is ergonomic. +For Paperclip it would be risky because it combines: + +- package installation +- code loading +- execution + +inside the control-plane server startup path. + +Paperclip should require an explicit operator install step. + +## Why Paperclip Needs A Different Shape + +The products are solving different problems. + +| Topic | OpenCode | Paperclip | +|---|---|---| +| Primary unit | local project/worktree | single-tenant operator instance with company objects | +| Trust assumption | local power user on own machine | operator managing one trusted Paperclip instance | +| Failure blast radius | local session/runtime | entire company control plane | +| Extension style | mutate runtime behavior freely | preserve governance and auditability | +| UI model | local app can load local behavior | board UI must stay coherent and safe | +| Security model | host-trusted local plugins | needs capability boundaries and auditability | + +That means Paperclip should borrow the good ideas from `opencode` but use a stricter architecture. + +## Paperclip Already Has Useful Pre-Plugin Seams + +Paperclip has several extension-like seams already: + +- server adapter registry: [server/src/adapters/registry.ts](../../server/src/adapters/registry.ts) +- UI adapter registry: [ui/src/adapters/registry.ts](../../ui/src/adapters/registry.ts) +- storage provider registry: [server/src/storage/provider-registry.ts](../../server/src/storage/provider-registry.ts) +- secret provider registry: [server/src/secrets/provider-registry.ts](../../server/src/secrets/provider-registry.ts) +- pluggable run-log store seam: [server/src/services/run-log-store.ts](../../server/src/services/run-log-store.ts) +- activity log and live event emission: [server/src/services/activity-log.ts](../../server/src/services/activity-log.ts) + +This is good news. +Paperclip does not need to invent extensibility from scratch. +It needs to unify and harden existing seams. + +## Recommended Paperclip Plugin Model + +## 1. Use multiple extension classes + +Do not create one giant `hooks` object for everything. + +Use distinct plugin classes with different trust models. + +| Extension class | Examples | Runtime model | Trust level | Why | +|---|---|---|---|---| +| Platform module | agent adapters, storage providers, secret providers, run-log backends | in-process | highly trusted | tight integration, performance, low-level APIs | +| Connector plugin | Linear, GitHub Issues, Grafana, Stripe | out-of-process worker or sidecar | medium | external sync, safer isolation, clearer failure boundary | +| Workspace plugin | file browser, terminal, git workflow, child process/server tracking | mixed: core workspace services plus plugin descriptors | high | needs local OS/workspace primitives but should reuse core services | +| UI contribution | dashboard widgets, settings forms, company panels | schema-driven first, remote React later if needed | medium | safer than arbitrary frontend code | +| Automation plugin | alerts, schedulers, sync jobs, webhook processors | out-of-process | medium | event-driven automation is a natural plugin fit | + +This split is the most important design recommendation in this report. + +## 2. Keep low-level modules separate from third-party plugins + +Paperclip already has this pattern implicitly: + +- adapters are one thing +- storage providers are another +- secret providers are another + +Keep that separation. + +I would formalize it like this: + +- `module` means trusted code loaded by the host for low-level runtime services +- `plugin` means integration code that talks to Paperclip through a typed plugin protocol and capability model + +This avoids trying to force Stripe, a PTY terminal, and a new agent adapter into the same abstraction. + +## 3. Prefer event-driven extensions over core-logic mutation + +For third-party plugins, the primary API should be: + +- subscribe to typed domain events +- read instance state, including company-bound business records when relevant +- register webhooks +- run scheduled jobs +- write plugin-owned state +- add additive UI surfaces +- invoke explicit Paperclip actions through the API + +Do not make third-party plugins responsible for: + +- deciding whether an approval passes +- intercepting issue checkout semantics +- rewriting activity log behavior +- overriding budget hard-stops + +Those are core invariants. + +## 4. Start with schema-driven UI contributions + +Arbitrary third-party React bundles inside the board UI are possible later, but they should not be the first version. + +First version should let plugins contribute: + +- settings sections defined by JSON schema +- dashboard widgets with server-provided data +- sidebar entries with fixed shell rendering +- detail-page tabs that render plugin data through core UI components + +Why: + +- simpler to secure +- easier to keep visually coherent +- easier to preserve context and auditability +- easier to test + +Later, if needed, Paperclip can support richer frontend extensions through versioned remote modules. + +## 5. Make installation global and keep mappings/config separate + +`opencode` is mostly user-level local config. +Paperclip should treat plugin installation as a global instance-level action. + +Examples: + +- install `@paperclip/plugin-linear` once +- make it available everywhere immediately +- optionally store mappings over Paperclip objects if one company maps to a different Linear team than another + +## 6. Use project workspaces as the primary anchor for local tooling + +Paperclip already has a concrete workspace model for projects: + +- projects expose `workspaces` and `primaryWorkspace` +- the database already has `project_workspaces` +- project routes already support creating, updating, and deleting workspaces +- heartbeat resolution already prefers project workspaces before falling back to task-session or agent-home workspaces + +That means local/runtime plugins should generally anchor themselves to projects first, not invent a parallel workspace model. + +Practical guidance: + +- file browser should browse project workspaces first +- terminal sessions should be launchable from a project workspace +- git should treat the project workspace as the repo root anchor +- dev server and child-process tracking should attach to project workspaces +- issue and agent views can still deep-link into the relevant project workspace context + +In other words: + +- `project` is the business object +- `project_workspace` is the local runtime anchor +- plugins should build on that instead of creating an unrelated workspace model first + +## A Concrete SDK Shape For Paperclip + +An intentionally narrow first pass could look like this: + +```ts +import { definePlugin, z } from "@paperclipai/plugin-sdk"; + +export default definePlugin({ + id: "@paperclip/plugin-linear", + version: "0.1.0", + kind: ["connector", "ui"], + capabilities: [ + "events.subscribe", + "jobs.schedule", + "http.outbound", + "instance.settings", + "dashboard.widget", + "secrets.read-ref", + ], + instanceConfigSchema: z.object({ + linearBaseUrl: z.string().url().optional(), + companyMappings: z.array( + z.object({ + companyId: z.string(), + teamId: z.string(), + apiTokenSecretRef: z.string(), + }), + ).default([]), + }), + async register(ctx) { + ctx.jobs.register("linear-pull", { cron: "*/5 * * * *" }, async (job) => { + // sync Linear issues into plugin-owned state or explicit Paperclip entities + }); + + ctx.events.on("issue.created", async (event) => { + // optional outbound sync + }); + + ctx.ui.registerDashboardWidget({ + id: "linear-health", + title: "Linear", + loader: async ({ companyId }) => ({ status: "ok" }), + }); + }, +}); +``` + +The important point is not the exact syntax. +The important point is the contract shape: + +- typed manifest +- explicit capabilities +- explicit global config with optional company mappings +- event subscriptions +- jobs +- additive UI contributions + +## Recommended Core Extension Surfaces + +## 1. Platform module surfaces + +These should stay close to the current registry style. + +Candidates: + +- `registerAgentAdapter()` +- `registerStorageProvider()` +- `registerSecretProvider()` +- `registerRunLogStore()` +- maybe `registerWorkspaceRuntime()` later + +These are trusted platform modules, not casual plugins. + +## 2. Connector plugin surfaces + +These are the best near-term plugin candidates. + +Capabilities: + +- subscribe to domain events +- define scheduled sync jobs +- expose plugin-specific API routes under `/api/plugins/:pluginId/...` +- use company secret refs +- write plugin state +- publish dashboard data +- log activity through core APIs + +Examples: + +- Linear issue sync +- GitHub issue sync +- Grafana dashboard cards +- Stripe MRR / subscription rollups + +## 3. Workspace-runtime surfaces + +Your local-ops examples need first-party primitives plus plugin contributions. + +Examples: + +- file browser +- terminal +- git workflow +- child process tracking +- local dev server tracking + +These should not be arbitrary third-party code directly poking the host filesystem and PTY layer through ad-hoc hooks. +Instead, Paperclip should add first-party services such as: + +- project workspace service built on `project_workspaces` +- PTY session service +- process registry +- git service +- dev-server registry + +Then plugins can add: + +- UI panels on top of those services +- automations +- annotations +- external sync logic + +This keeps sensitive local-machine behavior centralized and auditable. + +## 4. UI contribution surfaces + +Recommended first version: + +- dashboard widgets +- settings panels +- detail-page tabs +- sidebar sections +- action buttons that invoke plugin routes + +Recommended later version: + +- richer remote UI modules + +Recommended never or only with extreme caution: + +- arbitrary override of core pages +- arbitrary replacement of routing/auth/layout logic + +## Governance And Safety Requirements + +Any Paperclip plugin system has to preserve core control-plane invariants from the repo docs. + +That means: + +- plugin install is global to the instance +- "companies" remain business objects in the API and data model, not tenant boundaries +- approval gates remain core-owned +- budget hard-stops remain core-owned +- mutating actions are activity-logged +- secrets remain ref-based and redacted in logs + +I would require the following for every plugin: + +## 1. Capability declaration + +Every plugin declares a static capability set such as: + +- `companies.read` +- `issues.read` +- `issues.write` +- `events.subscribe` +- `jobs.schedule` +- `http.outbound` +- `webhooks.receive` +- `assets.read` +- `assets.write` +- `workspace.pty` +- `workspace.fs.read` +- `workspace.fs.write` +- `secrets.read-ref` + +The board/operator sees this before installation. + +## 2. Global installation + +A plugin is installed once and becomes available across the instance. +If it needs mappings over specific Paperclip objects, those are plugin data, not enable/disable boundaries. + +## 3. Activity logging + +Plugin-originated mutations should flow through the same activity log mechanism, with actor identity like: + +- `actor_type = system` +- `actor_id = plugin:@paperclip/plugin-linear` + +or a dedicated `plugin` actor type if you want stronger semantics later. + +## 4. Health and failure reporting + +Each plugin should expose: + +- enabled/disabled state +- last successful run +- last error +- recent webhook/job history + +One broken plugin must not break the rest of the company. + +## 5. Secret handling + +Plugins should receive secret refs, not raw secret values in config persistence. +Resolution should go through the existing secret provider abstraction. + +## 6. Resource limits + +Plugins should have: + +- timeout limits +- concurrency limits +- retry policies +- optional per-plugin budgets + +This matters especially for sync connectors and workspace plugins. + +## Data Model Additions To Consider + +I would avoid "arbitrary third-party plugin-defined SQL migrations" in the first version. +That is too much power too early. + +The right mental model is: + +- reuse core tables when the data is clearly part of Paperclip itself +- use generic extension tables for most plugin-owned state +- only allow plugin-specific tables later, and only for trusted platform modules or a tightly controlled migration workflow + +## Recommended Postgres Strategy For Extensions + +### 1. Core tables stay core + +If a concept is becoming part of Paperclip's actual product model, it should get a normal first-party table. + +Examples: + +- `project_workspaces` is already a core table because project workspaces are now part of Paperclip itself +- if a future "project git state" becomes a core feature rather than plugin-owned metadata, that should also be a first-party table + +### 2. Most plugins should start in generic extension tables + +For most plugins, the host should provide a few generic persistence tables and the plugin stores namespaced records there. + +This keeps the system manageable: + +- simpler migrations +- simpler backup/restore +- simpler portability story +- easier operator review +- fewer chances for plugin schema drift to break the instance + +### 3. Scope plugin data to Paperclip objects before adding custom schemas + +A lot of plugin data naturally hangs off existing Paperclip objects: + +- project workspace plugin state should often scope to `project` or `project_workspace` +- issue sync state should scope to `issue` +- metrics widgets may scope to `company`, `project`, or `goal` +- process tracking may scope to `project_workspace`, `agent`, or `run` + +That gives a good default keying model before introducing custom tables. + +### 4. Add trusted module migrations later, not arbitrary plugin migrations now + +If Paperclip eventually needs extension-owned tables, I would only allow that for: + +- trusted first-party packages +- trusted platform modules +- maybe explicitly installed admin-reviewed plugins with pinned versions + +I would not let random third-party plugins run free-form schema migrations on startup. + +Instead, add a controlled mechanism later if it becomes necessary. + +## Suggested baseline extension tables + +## 1. `plugins` + +Instance-level installation record. + +Suggested fields: + +- `id` +- `package_name` +- `version` +- `kind` +- `manifest_json` +- `installed_at` +- `status` + +## 2. `plugin_config` + +Instance-level plugin config. + +Suggested fields: + +- `id` +- `plugin_id` +- `config_json` +- `installed_at` +- `updated_at` +- `last_error` + +## 3. `plugin_state` + +Generic key/value state for plugins. + +Suggested fields: + +- `id` +- `plugin_id` +- `scope_kind` (`instance | company | project | project_workspace | agent | issue | goal | run`) +- `scope_id` nullable +- `namespace` +- `state_key` +- `value_json` +- `updated_at` + +This is enough for many connectors before allowing custom tables. + +Examples: + +- Linear external IDs keyed by `issue` +- GitHub sync cursors keyed by `project` +- file browser preferences keyed by `project_workspace` +- git branch metadata keyed by `project_workspace` +- process metadata keyed by `project_workspace` or `run` + +## 4. `plugin_jobs` + +Scheduled job and run tracking. + +Suggested fields: + +- `id` +- `plugin_id` +- `scope_kind` nullable +- `scope_id` nullable +- `job_key` +- `status` +- `last_started_at` +- `last_finished_at` +- `last_error` + +## 5. `plugin_webhook_deliveries` + +If plugins expose webhooks, delivery history is worth storing. + +Suggested fields: + +- `id` +- `plugin_id` +- `scope_kind` nullable +- `scope_id` nullable +- `endpoint_key` +- `status` +- `received_at` +- `response_code` +- `error` + +## 6. Maybe later: `plugin_entities` + +If generic plugin state becomes too limiting, add a structured, queryable entity table for connector records before allowing arbitrary plugin migrations. + +Suggested fields: + +- `id` +- `plugin_id` +- `entity_type` +- `scope_kind` +- `scope_id` +- `external_id` +- `title` +- `status` +- `data_json` +- `updated_at` + +This is a useful middle ground: + +- much more queryable than opaque key/value state +- still avoids letting every plugin create its own relational schema immediately + +## How The Requested Examples Map To This Model + +| Use case | Best fit | Core primitives needed | Notes | +|---|---|---|---| +| File browser | workspace plugin + schema UI | project workspaces, file API, audit rules | best anchored on project detail pages | +| Terminal | workspace plugin + PTY service | project workspaces, PTY/session service, process limits, audit events | should launch against a project workspace by default | +| Git workflow | workspace plugin | project workspaces, git service, repo/worktree model | project workspace is the natural repo anchor | +| Linear issue tracking | connector plugin | jobs, webhooks, secret refs, issue sync API | very strong plugin candidate | +| GitHub issue tracking | connector plugin | jobs, webhooks, secret refs | very strong plugin candidate | +| Grafana metrics | connector plugin + dashboard widget | outbound HTTP, widget API | probably read-only first | +| Child process/server tracking | workspace plugin | project workspaces, process registry, server heartbeat model | should attach processes to project workspaces when possible | +| Stripe revenue tracking | connector plugin | secret refs, scheduled sync, company metrics API | strong plugin candidate and aligns with future spec direction | + +# Plugin Examples + +## Workspace File Browser + +Package idea: `@paperclip/plugin-workspace-files` + +This plugin lets the board inspect project workspaces, agent workspaces, generated artifacts, and issue-related files without dropping to the shell. It is useful for: + +- browsing files inside project workspaces +- debugging what an agent changed +- reviewing generated outputs before approval +- attaching files from a workspace to issues +- understanding repo layout for a company +- inspecting agent home workspaces in local-trusted mode + +### UX + +- Settings page: `/settings/plugins/workspace-files` +- Main page: `/:companyPrefix/plugins/workspace-files` +- Project tab: `/:companyPrefix/projects/:projectId?tab=files` +- Optional issue tab: `/:companyPrefix/issues/:issueId?tab=files` +- Optional agent tab: `/:companyPrefix/agents/:agentId?tab=workspace` + +Main screens and interactions: + +- Plugin settings: + - choose whether the plugin defaults to `project.primaryWorkspace` + - choose which project workspaces are visible + - choose whether file writes are allowed or read-only + - choose whether hidden files are visible +- Main explorer page: + - project picker at the top + - workspace picker scoped to the selected project's `workspaces` + - tree view on the left + - file preview pane on the right + - search box for filename/path search + - actions: copy path, download file, attach to issue, open diff +- Project tab: + - opens directly into the project's primary workspace + - lets the board switch among all project workspaces + - shows workspace metadata like `cwd`, `repoUrl`, and `repoRef` +- Issue tab: + - resolves the issue's project and opens that project's workspace context + - shows files linked to the issue + - lets the board pull files from the project workspace into issue attachments + - shows the path and last modified info for each linked file +- Agent tab: + - shows the agent's current resolved workspace + - if the run is attached to a project, links back to the project workspace view + - lets the board inspect files the agent is currently touching + +Core workflows: + +- Board opens a project and browses its primary workspace files. +- Board switches from one project workspace to another when a project has multiple checkouts or repo references. +- Board opens an issue, attaches a generated artifact from the file browser, and leaves a review comment. +- Board opens an agent detail page to inspect the exact files behind a failing run. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.detailTab.register` for `project`, `issue`, and `agent` +- `projects.read` +- `project.workspaces.read` +- `workspace.fs.read` +- optional `workspace.fs.write` +- `workspace.fs.stat` +- `workspace.fs.search` +- optional `assets.write` +- `activity.log.write` + +Optional event subscriptions: + +- `events.subscribe(agent.run.started)` +- `events.subscribe(agent.run.finished)` +- `events.subscribe(issue.attachment.created)` + +Important constraint: + +- the plugin should never read arbitrary host paths directly +- it should treat project workspaces as the canonical local file roots when a project is present +- it should only use first-party workspace/file APIs that enforce approved workspace roots + +## Workspace Terminal + +Package idea: `@paperclip/plugin-terminal` + +This plugin gives the board a controlled terminal UI for project workspaces and agent workspaces. It is useful for: + +- debugging stuck runs +- verifying environment state +- running targeted manual commands +- watching long-running commands +- pairing a human operator with an agent workflow + +### UX + +- Settings page: `/settings/plugins/terminal` +- Main page: `/:companyPrefix/plugins/terminal` +- Project tab: `/:companyPrefix/projects/:projectId?tab=terminal` +- Optional agent tab: `/:companyPrefix/agents/:agentId?tab=terminal` +- Optional run tab: `/:companyPrefix/agents/:agentId/runs/:runId?tab=terminal` + +Main screens and interactions: + +- Plugin settings: + - allowed shells and shell policy + - whether commands are read-only, free-form, or allow-listed + - whether terminals require an explicit operator confirmation before launch + - whether new terminal sessions default to the project's primary workspace +- Terminal home page: + - list of active terminal sessions + - button to open a new session + - project picker, then workspace picker from that project's workspaces + - optional agent association + - terminal panel with input, resize, and reconnect support + - controls: interrupt, kill, clear, save transcript +- Project terminal tab: + - opens a session already scoped to the project's primary workspace + - lets the board switch among the project's configured workspaces + - shows recent commands and related process/server state for that project +- Agent terminal tab: + - opens a session already scoped to the agent's workspace + - shows recent related runs and commands +- Run terminal tab: + - lets the board inspect the environment around a specific failed run + +Core workflows: + +- Board opens a terminal against an agent workspace to reproduce a failing command. +- Board opens a project page and launches a terminal directly in that project's primary workspace. +- Board watches a long-running dev server or test command from the terminal page. +- Board kills or interrupts a runaway process from the same UI. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.detailTab.register` for `project`, `agent`, and `run` +- `projects.read` +- `project.workspaces.read` +- `workspace.pty.open` +- `workspace.pty.input` +- `workspace.pty.resize` +- `workspace.pty.terminate` +- `workspace.pty.subscribe` +- `workspace.process.read` +- `activity.log.write` + +Optional event subscriptions: + +- `events.subscribe(agent.run.started)` +- `events.subscribe(agent.run.failed)` +- `events.subscribe(agent.run.cancelled)` + +Important constraint: + +- shell spawning must stay in a first-party PTY service +- the plugin should orchestrate and render sessions, not spawn raw host processes by itself + +## Git Workflow + +Package idea: `@paperclip/plugin-git` + +This plugin adds repo-aware workflow tooling around issues and workspaces. It is useful for: + +- branch creation tied to issues +- quick diff review +- commit and worktree visibility +- PR preparation +- treating the project's primary workspace as the canonical repo anchor +- seeing whether an agent's workspace is clean or dirty + +### UX + +- Settings page: `/settings/plugins/git` +- Main page: `/:companyPrefix/plugins/git` +- Project tab: `/:companyPrefix/projects/:projectId?tab=git` +- Optional issue tab: `/:companyPrefix/issues/:issueId?tab=git` +- Optional agent tab: `/:companyPrefix/agents/:agentId?tab=git` + +Main screens and interactions: + +- Plugin settings: + - branch naming template + - optional remote provider token secret ref + - whether write actions are enabled or read-only + - whether the plugin always uses `project.primaryWorkspace` unless a different project workspace is chosen +- Git overview page: + - project picker and workspace picker + - current branch + - ahead/behind status + - dirty files summary + - recent commits + - active worktrees + - actions: refresh, create branch, create worktree, stage all, commit, open diff +- Project tab: + - opens in the project's primary workspace + - shows workspace metadata and repo binding (`cwd`, `repoUrl`, `repoRef`) + - shows branch, diff, and commit history for that project workspace +- Issue tab: + - resolves the issue's project and uses that project's workspace context + - "create branch from issue" action + - diff view scoped to the project's selected workspace + - link branch/worktree metadata to the issue +- Agent tab: + - shows the agent's branch, worktree, and dirty state + - shows recent commits produced by that agent + - if the agent is working inside a project workspace, links back to the project git tab + +Core workflows: + +- Board creates a branch from an issue and ties it to the project's primary workspace. +- Board opens a project page and reviews the diff for that project's workspace without leaving Paperclip. +- Board reviews the diff after a run without leaving Paperclip. +- Board opens a worktree list to understand parallel branches across agents. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.detailTab.register` for `project`, `issue`, and `agent` +- `ui.action.register` +- `projects.read` +- `project.workspaces.read` +- `workspace.git.status` +- `workspace.git.diff` +- `workspace.git.log` +- `workspace.git.branch.create` +- optional `workspace.git.commit` +- optional `workspace.git.worktree.create` +- optional `workspace.git.push` +- `activity.log.write` + +Optional event subscriptions: + +- `events.subscribe(issue.created)` +- `events.subscribe(issue.updated)` +- `events.subscribe(agent.run.finished)` + +Important constraint: + +- GitHub/GitLab PR creation should likely live in a separate connector plugin rather than overloading the local git plugin + +## Linear Issue Tracking + +Package idea: `@paperclip/plugin-linear` + +This plugin syncs Paperclip work with Linear. It is useful for: + +- importing backlog from Linear +- linking Paperclip issues to Linear issues +- syncing status, comments, and assignees +- mapping company goals/projects to external product planning +- giving board operators a single place to see sync health + +### UX + +- Settings page: `/settings/plugins/linear` +- Main page: `/:companyPrefix/plugins/linear` +- Dashboard widget: `/:companyPrefix/dashboard` +- Optional issue tab: `/:companyPrefix/issues/:issueId?tab=linear` +- Optional project tab: `/:companyPrefix/projects/:projectId?tab=linear` + +Main screens and interactions: + +- Plugin settings: + - Linear API token secret ref + - workspace/team/project mappings + - status mapping between Paperclip and Linear + - sync direction: import only, export only, bidirectional + - comment sync toggle +- Linear overview page: + - sync health card + - recent sync jobs + - mapped projects and teams + - unresolved conflicts queue + - import actions for teams, projects, and issues +- Issue tab: + - linked Linear issue key and URL + - sync status and last synced time + - actions: link existing, create in Linear, resync now, unlink + - timeline of synced comments/status changes +- Dashboard widget: + - open sync errors + - imported vs linked issues count + - recent webhook/job failures + +Core workflows: + +- Board enables the plugin, maps a Linear team, and imports a backlog into Paperclip. +- Paperclip issue status changes push to Linear and Linear comments arrive back through webhooks. +- Board resolves mapping conflicts from the plugin page instead of silently drifting state. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.dashboardWidget.register` +- `ui.detailTab.register` for `issue` and `project` +- `events.subscribe(issue.created)` +- `events.subscribe(issue.updated)` +- `events.subscribe(issue.comment.created)` +- `events.subscribe(project.updated)` +- `jobs.schedule` +- `webhooks.receive` +- `http.outbound` +- `secrets.read-ref` +- `plugin.state.read` +- `plugin.state.write` +- optional `issues.create` +- optional `issues.update` +- optional `issue.comments.create` +- `activity.log.write` + +Important constraint: + +- webhook processing should be idempotent and conflict-aware +- external IDs and sync cursors belong in plugin-owned state, not inline on core issue rows in the first version + +## GitHub Issue Tracking + +Package idea: `@paperclip/plugin-github-issues` + +This plugin syncs Paperclip issues with GitHub Issues and optionally links PRs. It is useful for: + +- importing repo backlogs +- mirroring issue status and comments +- linking PRs to Paperclip issues +- tracking cross-repo work from inside one company view +- bridging engineering workflow with Paperclip task governance + +### UX + +- Settings page: `/settings/plugins/github-issues` +- Main page: `/:companyPrefix/plugins/github-issues` +- Dashboard widget: `/:companyPrefix/dashboard` +- Optional issue tab: `/:companyPrefix/issues/:issueId?tab=github` +- Optional project tab: `/:companyPrefix/projects/:projectId?tab=github` + +Main screens and interactions: + +- Plugin settings: + - GitHub App or PAT secret ref + - org/repo mappings + - label/status mapping + - whether PR linking is enabled + - whether new Paperclip issues should create GitHub issues automatically +- GitHub overview page: + - repo mapping list + - sync health and recent webhook events + - import backlog action + - queue of unlinked GitHub issues +- Issue tab: + - linked GitHub issue and optional linked PRs + - actions: create GitHub issue, link existing issue, unlink, resync + - comment/status sync timeline +- Dashboard widget: + - open PRs linked to active Paperclip issues + - webhook failures + - sync lag metrics + +Core workflows: + +- Board imports GitHub Issues for a repo into Paperclip. +- GitHub webhooks update status/comment state in Paperclip. +- A PR is linked back to the Paperclip issue so the board can follow delivery status. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.dashboardWidget.register` +- `ui.detailTab.register` for `issue` and `project` +- `events.subscribe(issue.created)` +- `events.subscribe(issue.updated)` +- `events.subscribe(issue.comment.created)` +- `jobs.schedule` +- `webhooks.receive` +- `http.outbound` +- `secrets.read-ref` +- `plugin.state.read` +- `plugin.state.write` +- optional `issues.create` +- optional `issues.update` +- optional `issue.comments.create` +- `activity.log.write` + +Important constraint: + +- keep "local git state" and "remote GitHub issue state" in separate plugins even if they work together + +## Grafana Metrics + +Package idea: `@paperclip/plugin-grafana` + +This plugin surfaces external metrics and dashboards inside Paperclip. It is useful for: + +- company KPI visibility +- infrastructure/incident monitoring +- showing deploy, traffic, latency, or revenue charts next to work +- creating Paperclip issues from anomalous metrics + +### UX + +- Settings page: `/settings/plugins/grafana` +- Main page: `/:companyPrefix/plugins/grafana` +- Dashboard widgets: `/:companyPrefix/dashboard` +- Optional goal tab: `/:companyPrefix/goals/:goalId?tab=metrics` + +Main screens and interactions: + +- Plugin settings: + - Grafana base URL + - service account token secret ref + - dashboard and panel mappings + - refresh interval + - optional alert threshold rules +- Dashboard widgets: + - one or more metric cards on the main dashboard + - quick trend view and last refresh time + - link out to Grafana and link in to the full Paperclip plugin page +- Full metrics page: + - selected dashboard panels embedded or proxied + - metric selector + - time range selector + - "create issue from anomaly" action +- Goal tab: + - metric cards relevant to a specific goal or project + +Core workflows: + +- Board sees service degradation or business KPI movement directly on the Paperclip dashboard. +- Board clicks into the full metrics page to inspect the relevant Grafana panels. +- Board creates a Paperclip issue from a threshold breach with a metric snapshot attached. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.dashboardWidget.register` +- `ui.page.register` +- `ui.detailTab.register` for `goal` or `project` +- `jobs.schedule` +- `http.outbound` +- `secrets.read-ref` +- `plugin.state.read` +- `plugin.state.write` +- optional `issues.create` +- optional `assets.write` +- `activity.log.write` + +Optional event subscriptions: + +- `events.subscribe(goal.created)` +- `events.subscribe(project.updated)` + +Important constraint: + +- start read-only first +- do not make Grafana alerting logic part of Paperclip core; keep it as additive signal and issue creation + +## Child Process / Server Tracking + +Package idea: `@paperclip/plugin-runtime-processes` + +This plugin tracks long-lived local processes and dev servers started in project workspaces. It is useful for: + +- seeing which agent started which local service +- tracking ports, health, and uptime +- restarting failed dev servers +- exposing process state alongside issue and run state +- making local development workflows visible to the board + +### UX + +- Settings page: `/settings/plugins/runtime-processes` +- Main page: `/:companyPrefix/plugins/runtime-processes` +- Dashboard widget: `/:companyPrefix/dashboard` +- Process detail page: `/:companyPrefix/plugins/runtime-processes/:processId` +- Project tab: `/:companyPrefix/projects/:projectId?tab=processes` +- Optional agent tab: `/:companyPrefix/agents/:agentId?tab=processes` + +Main screens and interactions: + +- Plugin settings: + - whether manual process registration is allowed + - health check behavior + - whether operators can stop/restart processes + - log retention preferences +- Process list page: + - status table with name, command, cwd, owner agent, port, uptime, and health + - filters for running/exited/crashed processes + - actions: inspect, stop, restart, tail logs +- Project tab: + - filters the process list to the project's workspaces + - shows which workspace each process belongs to + - groups processes by project workspace +- Process detail page: + - process metadata + - live log tail + - health check history + - links to associated issue or run +- Agent tab: + - shows processes started by or assigned to that agent + +Core workflows: + +- An agent starts a dev server; the first-party process service registers it and the plugin renders it. +- Board opens a project and immediately sees the processes attached to that project's workspace. +- Board sees a crashed process on the dashboard and restarts it from the plugin page. +- Board attaches process logs to an issue when debugging a failure. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.sidebar.register` +- `ui.page.register` +- `ui.dashboardWidget.register` +- `ui.detailTab.register` for `project` and `agent` +- `projects.read` +- `project.workspaces.read` +- `workspace.process.register` +- `workspace.process.list` +- `workspace.process.read` +- optional `workspace.process.terminate` +- optional `workspace.process.restart` +- `workspace.process.logs.read` +- optional `workspace.http.probe` +- `plugin.state.read` +- `plugin.state.write` +- `activity.log.write` + +Optional event subscriptions: + +- `events.subscribe(agent.run.started)` +- `events.subscribe(agent.run.finished)` +- `events.subscribe(process.started)` +- `events.subscribe(process.exited)` + +Important constraint: + +- this should be built on a first-party process registry +- the plugin should not own raw child-process spawning on its own + +## Stripe Revenue Tracking + +Package idea: `@paperclip/plugin-stripe` + +This plugin pulls Stripe revenue and subscription data into Paperclip. It is useful for: + +- showing MRR and churn next to company goals +- tracking trials, conversions, and failed payments +- letting the board connect revenue movement to ongoing work +- enabling future financial dashboards beyond token costs + +### UX + +- Settings page: `/settings/plugins/stripe` +- Main page: `/:companyPrefix/plugins/stripe` +- Dashboard widgets: `/:companyPrefix/dashboard` +- Optional company/goal metric tabs if those surfaces exist later + +Main screens and interactions: + +- Plugin settings: + - Stripe secret key secret ref + - account selection if needed + - metric definitions such as MRR treatment and trial handling + - sync interval + - webhook signing secret ref +- Dashboard widgets: + - MRR card + - active subscriptions + - trial-to-paid conversion + - failed payment alerts +- Stripe overview page: + - time series charts + - recent customer/subscription events + - webhook health + - sync history + - action: create issue from billing anomaly + +Core workflows: + +- Board enables the plugin and connects a Stripe account. +- Webhooks and scheduled reconciliation keep plugin state current. +- Revenue widgets appear on the main dashboard and can be linked to company goals. +- Failed payment spikes or churn events can generate Paperclip issues for follow-up. + +### Hooks needed + +Recommended capabilities and extension points: + +- `instance.settings.register` +- `ui.dashboardWidget.register` +- `ui.page.register` +- `jobs.schedule` +- `webhooks.receive` +- `http.outbound` +- `secrets.read-ref` +- `plugin.state.read` +- `plugin.state.write` +- `company.metrics.write` +- optional `issues.create` +- `activity.log.write` + +Important constraint: + +- Stripe data should stay additive to Paperclip core +- it should not leak into core budgeting logic, which is specifically about model/token spend in V1 + +## Specific Patterns From OpenCode Worth Adopting + +## Adopt + +- separate SDK package from runtime loader +- deterministic load order and precedence +- very small authoring API +- typed schemas for plugin inputs/config/tools +- internal extensions using the same registration shapes as external ones when reasonable +- plugin load errors isolated from host startup when possible +- explicit community-facing plugin docs and example templates + +## Adapt, not copy + +- local path loading +- dependency auto-install +- hook mutation model +- built-in override behavior +- broad runtime context objects + +## Avoid + +- project-local arbitrary code loading +- implicit trust of npm packages at startup +- plugins overriding core invariants +- unsandboxed in-process execution as the default extension model + +## Suggested Rollout Plan + +## Phase 0: Harden the seams that already exist + +- formalize adapter/storage/secret/run-log registries as "platform modules" +- remove ad-hoc fallback behavior where possible +- document stable registration contracts + +## Phase 1: Add connector plugins first + +This is the highest-value, lowest-risk plugin category. + +Build: + +- plugin manifest +- global install/update lifecycle +- global plugin config and optional company-mapping storage +- secret ref access +- typed domain event subscription +- scheduled jobs +- webhook endpoints +- activity logging helpers +- dashboard widget and settings-panel contributions + +This phase would immediately cover: + +- Linear +- GitHub +- Grafana +- Stripe + +## Phase 2: Add workspace services and workspace plugins + +Build first-party primitives: + +- project workspace service built on `project_workspaces` +- PTY/session service +- file service +- git service +- process/service tracker + +Then expose additive plugin/UI surfaces on top. + +This phase covers: + +- file browser +- terminal +- git workflow +- child process/server tracking + +## Phase 3: Consider richer UI and plugin packaging + +Only after phases 1 and 2 are stable: + +- richer frontend extension support +- signed/verified plugin packages +- plugin marketplace +- optional custom plugin storage backends or migrations + +## Recommended Architecture Decision + +If I had to collapse this report into one architectural decision, it would be: + +Paperclip should not implement "an OpenCode-style generic in-process hook system." +Paperclip should implement "a plugin platform with multiple trust tiers": + +- trusted platform modules for low-level runtime integration +- typed out-of-process plugins for instance-wide integrations and automation +- schema-driven UI contributions +- core-owned invariants that plugins can observe and act around, but not replace + +That gets the upside of `opencode`'s extensibility without importing the wrong threat model. + +## Concrete Next Steps I Would Take In Paperclip + +1. Write a short extension architecture RFC that formalizes the distinction between `platform modules` and `plugins`. +2. Introduce a small plugin manifest type in `packages/shared` and a `plugins` install/config section in the instance config. +3. Build a typed domain event bus around existing activity/live-event patterns, but keep core invariants non-hookable. +4. Implement connector-plugin MVP only: global install/config, secret refs, jobs, webhooks, settings panel, dashboard widget. +5. Treat workspace features as a separate track that starts by building core workspace primitives, not raw plugin hooks.