Files
paperclip/doc/plugins/ideas-from-opencode.md
2026-03-07 17:16:37 -06:00

1638 lines
52 KiB
Markdown

# 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.