1638 lines
52 KiB
Markdown
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.
|