Files
anomalyco_opencode/packages/opencode/specs/plugin-architecture.md
T
2026-04-17 16:29:02 -04:00

194 lines
5.6 KiB
Markdown

# Plugin architecture
This is a working note for reorganizing plugin code while the codebase migrates to Effect.
## Current shape
The main problem is that one conceptual system is split across a few large modules with overlapping responsibilities.
```mermaid
flowchart TD
A[ConfigPlugin origins] --> B[plugin/shared.ts]
A --> C[plugin/loader.ts]
A --> D[plugin/install.ts]
B --> C
B --> E[plugin/index.ts server runtime]
B --> F[tui/plugin/runtime.ts TUI runtime]
B --> D
C --> E
C --> F
D --> G[cli/cmd/plug.ts]
D --> F
H[plugin/meta.ts] --> F
I[npm/index.ts] --> B
I --> C
I --> D
style B fill:#3a2f1f,stroke:#c98a2b,color:#fff
style C fill:#3a2f1f,stroke:#c98a2b,color:#fff
style D fill:#3a2f1f,stroke:#c98a2b,color:#fff
style E fill:#4a1f1f,stroke:#d46a6a,color:#fff
style F fill:#4a1f1f,stroke:#d46a6a,color:#fff
```
### What is mixed together today
- `src/plugin/shared.ts`
spec parsing, package reading, entry resolution, compatibility checks, theme discovery, module validation, id resolution
- `src/plugin/loader.ts`
plan building, target resolution, import, retry rules, reporting hooks
- `src/plugin/install.ts`
install wrapper, manifest inspection, config patching, file locking
- `src/plugin/index.ts`
server plugin runtime, hook loading, config fanout, event subscription
- `src/cli/cmd/tui/plugin/runtime.ts`
TUI runtime state, loading, activation, API adaptation, theme sync, install flow, pending state, process singleton
- `src/plugin/meta.ts`
file-backed mutable plugin metadata store
## Target shape
The redesign should split stateless plugin plumbing from stateful runtimes.
```mermaid
flowchart TD
subgraph Pure[Pure helpers]
S1[plugin/spec.ts]
S2[plugin/module.ts]
S3[plugin/manifest.ts]
end
subgraph Effects[Effect functions]
E1[plugin/package.ts]
E2[plugin/external.ts]
E3[plugin/install.ts]
T2[tui/plugin/theme.ts]
T3[tui/plugin/api.ts]
T4[tui/plugin/scope.ts]
T5[tui/plugin/activation.ts]
end
subgraph Services[Effect services]
SV1[plugin/meta-store.ts PluginMetaStore.Service]
SV2[plugin/server.ts PluginServer.Service]
SV3[tui/plugin/manager.ts TuiPluginManager.Service]
end
Cfg[ConfigPlugin Origins] --> S1
S1 --> E1
S1 --> E2
S2 --> E2
S3 --> E3
E1 --> E2
E1 --> E3
E2 --> SV2
E2 --> SV3
T2 --> SV3
T3 --> SV3
T4 --> SV3
T5 --> SV3
SV1 --> SV3
E3 --> CLI[cli/cmd/plug.ts]
E3 --> SV3
style Pure fill:#1f3a2a,stroke:#4fa06b,color:#fff
style Effects fill:#1f2f4a,stroke:#5c8fda,color:#fff
style Services fill:#3b1f4a,stroke:#b070d6,color:#fff
```
## Module boundaries
### Pure helpers
- `src/plugin/spec.ts`
parse specifiers, detect npm vs file, normalize ids
- `src/plugin/module.ts`
validate exported module shape, extract `id`, read v1 server or TUI modules
- `src/plugin/manifest.ts`
derive package capabilities from package metadata
These should not touch the filesystem or global state.
### Effect functions
- `src/plugin/package.ts`
read `package.json`, check compatibility, read theme files
- `src/plugin/external.ts`
resolve targets, resolve entrypoints, import modules, retry local file plugins after dependency prep
- `src/plugin/install.ts`
shared install and config-patch workflow used by CLI and TUI
- `src/cli/cmd/tui/plugin/theme.ts`
sync and persist themes
- `src/cli/cmd/tui/plugin/api.ts`
adapt host API to plugin API
- `src/cli/cmd/tui/plugin/scope.ts`
lifecycle resource helpers
- `src/cli/cmd/tui/plugin/activation.ts`
activate and deactivate one plugin entry
These are composable functions that return `Effect`, but do not own long-lived mutable state.
### Services
- `PluginMetaStore.Service`
owns the metadata file and lock-backed updates
- `PluginServer.Service`
owns loaded server hooks and bus subscription state per project/worktree via `InstanceState`
- `TuiPluginManager.Service`
owns loaded TUI entries, enabled state, pending installs, and activation lifecycle
## Runtime split
### Server side
```mermaid
flowchart LR
A[PluginServer.Service] --> B[build plugin input]
A --> C[load internal plugins]
A --> D[load external plugins]
D --> E[plugin/external.ts]
A --> F[notify config]
A --> G[subscribe bus events]
```
### TUI side
```mermaid
flowchart LR
A[TuiPluginManager.Service] --> B[load internal entries]
A --> C[load external entries]
C --> D[plugin/external.ts]
A --> E[track plugin metadata]
E --> F[PluginMetaStore.Service]
A --> G[activate and deactivate]
G --> H[tui/plugin/activation.ts]
A --> I[sync themes]
I --> J[tui/plugin/theme.ts]
A --> K[install and configure plugin]
K --> L[plugin/install.ts]
```
## Design rules
- Keep orchestration readable at the top level.
- Put state in services, not module globals.
- Prefer typed results over callback-driven reporting.
- Share install/configure workflow between CLI and TUI.
- Keep plugin discovery parallel, but keep activation and hook registration sequential.
- Preserve the special cases that already matter:
theme-only TUI plugins, legacy server plugins, local file-plugin retry after dependency prep.
## Suggested migration order
1. Split `shared.ts` into `spec.ts`, `module.ts`, `manifest.ts`, and `package.ts` without changing behavior.
2. Replace `loader.ts` with flat exports in `external.ts` and return typed result values instead of report callbacks.
3. Collapse duplicated install flow into one shared `plugin/install.ts` workflow used by CLI and TUI.
4. Convert `meta.ts` into `PluginMetaStore.Service`.
5. Shrink `plugin/index.ts` into a thin `PluginServer.Service` composition root.
6. Break up `tui/plugin/runtime.ts` and move its mutable runtime state into `TuiPluginManager.Service`.