|
| 1 | +# Development Guidelines for kubeopencode-plugins |
| 2 | + |
| 3 | +This repository contains officially supported OpenCode plugins for the [KubeOpenCode](https://github.com/kubeopencode/kubeopencode) project. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +**KubeOpenCode** is a Kubernetes-native AI Agent Platform that wraps [OpenCode](https://opencode.ai) with enterprise infrastructure — governance, RBAC, persistence, scheduling, and multi-tenant agent management. Agents run as Kubernetes Deployments with OpenCode as the coding engine inside each Pod. |
| 8 | + |
| 9 | +**This repo** (`kubeopencode-plugins`) houses first-party plugins that extend Agent capabilities. Each plugin is an independent npm package that follows OpenCode's plugin API and can be installed into any Agent via `spec.plugins`. |
| 10 | + |
| 11 | +> **IMPORTANT**: The OpenCode project source is at `../opencode/` and the KubeOpenCode project source is at `../kubeopencode/`. Always search local codebases before using web search. |
| 12 | +
|
| 13 | +## Key References |
| 14 | + |
| 15 | +- **OpenCode plugin API**: `../opencode/packages/plugin/src/index.ts` — defines `Plugin`, `PluginModule`, `Hooks`, `PluginInput` |
| 16 | +- **OpenCode plugin loader**: `../opencode/packages/opencode/src/plugin/index.ts` — how plugins are loaded and hooks are wired |
| 17 | +- **KubeOpenCode plugin install flow**: `../kubeopencode/cmd/kubeopencode/plugin_init.go` — the `plugin-init` init container that runs `npm install` |
| 18 | +- **KubeOpenCode Agent plugin spec**: `../kubeopencode/api/v1alpha1/agent_types.go` (lines 139-183) — CRD fields for declaring plugins |
| 19 | +- **Existing Slack plugin reference**: `../kubeopencode/plugins/slack/dist/` — the original Slack plugin shipped with KubeOpenCode |
| 20 | + |
| 21 | +## Plugin Architecture |
| 22 | + |
| 23 | +### How Plugins Work in KubeOpenCode |
| 24 | + |
| 25 | +1. Plugins are declared in the Agent spec: |
| 26 | + ```yaml |
| 27 | + spec: |
| 28 | + plugins: |
| 29 | + - name: "opencode-slack-plugin" |
| 30 | + target: server |
| 31 | + ``` |
| 32 | +
|
| 33 | +2. The controller creates a **plugin-init** init container that runs `npm install --production` into a shared `/plugins` volume. |
| 34 | + |
| 35 | +3. The executor container loads plugins via OpenCode's config `plugin` array using `file:///plugins/node_modules/<package>` paths. |
| 36 | + |
| 37 | +4. The executor container **does not need npm** — it reads pre-installed packages. |
| 38 | + |
| 39 | +### Plugin Targets |
| 40 | + |
| 41 | +- **`server`** (default): Runs inside `opencode serve`. Has access to `PluginInput.client` (full SDK: sessions, prompts, events). This is the target for all plugins in this repo. |
| 42 | +- **`tui`**: Runs during interactive terminal sessions. Provides UI extensions. |
| 43 | + |
| 44 | +### PluginInput |
| 45 | + |
| 46 | +Every server plugin receives a `PluginInput` with: |
| 47 | + |
| 48 | +| Field | Type | Description | |
| 49 | +|-------|------|-------------| |
| 50 | +| `client` | `OpencodeClient` | Full SDK client (session.create, session.prompt, event.subscribe, etc.). Calls bypass HTTP — direct function invocation inside the process. | |
| 51 | +| `project` | `Project` | Current project info | |
| 52 | +| `directory` | `string` | Working directory | |
| 53 | +| `worktree` | `string` | Git worktree root | |
| 54 | +| `serverUrl` | `URL` | Server URL | |
| 55 | +| `$` | `BunShell` | Bun shell API | |
| 56 | + |
| 57 | +### Available Hooks |
| 58 | + |
| 59 | +Plugins return a `Hooks` object. Key hooks used by plugins in this repo: |
| 60 | + |
| 61 | +| Hook | Description | |
| 62 | +|------|-------------| |
| 63 | +| `event` | Receives ALL bus events (session.idle, message.part.updated, permission.asked, server.instance.disposed, etc.) | |
| 64 | +| `tool` | Register custom tools | |
| 65 | +| `chat.context` | Inject context into LLM prompts | |
| 66 | +| `chat.message` | Intercept new messages | |
| 67 | +| `permission.ask` | Intercept permission requests | |
| 68 | + |
| 69 | +## Plugin Conventions |
| 70 | + |
| 71 | +### Module Format |
| 72 | + |
| 73 | +Use the `PluginModule` format with an `id` field: |
| 74 | + |
| 75 | +```typescript |
| 76 | +import type { PluginModule } from "@opencode-ai/plugin" |
| 77 | +
|
| 78 | +const plugin: PluginModule = { |
| 79 | + id: "my-plugin", |
| 80 | + server: async (input) => { |
| 81 | + // initialization |
| 82 | + return { |
| 83 | + event: async ({ event }) => { /* ... */ }, |
| 84 | + } |
| 85 | + }, |
| 86 | +} |
| 87 | +
|
| 88 | +export default plugin |
| 89 | +``` |
| 90 | + |
| 91 | +### Environment Variables |
| 92 | + |
| 93 | +- Plugins receive credentials via `process.env`, injected from Agent `spec.credentials` (Kubernetes Secrets) |
| 94 | +- If required env vars are missing, log a warning and return empty hooks `{}` |
| 95 | +- Never hard-fail — a misconfigured plugin should not crash the Agent |
| 96 | + |
| 97 | +### Logging |
| 98 | + |
| 99 | +Use `console.log` / `console.warn` / `console.error` with a consistent prefix: |
| 100 | + |
| 101 | +```typescript |
| 102 | +console.log("[my-plugin] Connected successfully") |
| 103 | +console.warn("[my-plugin] Heartbeat disabled: missing env vars") |
| 104 | +``` |
| 105 | + |
| 106 | +### Heartbeat (KubeOpenCode-specific) |
| 107 | + |
| 108 | +KubeOpenCode Agents support **standby mode** — auto-suspend after idle, auto-resume on new task. Plugins that maintain persistent connections (WebSocket, long-polling) must send heartbeat annotations to prevent unexpected auto-suspend: |
| 109 | + |
| 110 | +- Annotation: `kubeopencode.io/last-connection-active` |
| 111 | +- Interval: 60 seconds (match `ConnectionHeartbeatInterval` in controller) |
| 112 | +- Stop heartbeat after 5 minutes of inactivity to allow idle timer |
| 113 | +- Read ServiceAccount token from `/var/run/secrets/kubernetes.io/serviceaccount/token` |
| 114 | +- If AGENT_NAME/AGENT_NAMESPACE/K8s API are unavailable, silently disable heartbeat |
| 115 | + |
| 116 | +See `opencode-slack-plugin/src/index.ts` for the reference implementation. |
| 117 | + |
| 118 | +### Graceful Shutdown |
| 119 | + |
| 120 | +Listen for the `server.instance.disposed` event to clean up connections: |
| 121 | + |
| 122 | +```typescript |
| 123 | +if (evt.type === "server.instance.disposed") { |
| 124 | + heartbeat.stop() |
| 125 | + connection.disconnect() |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +### Multi-Instance Safety |
| 130 | + |
| 131 | +Plugins may run in multiple Agent pods simultaneously. Design for this: |
| 132 | + |
| 133 | +- All state is per-process (in-memory Maps, closures) — no shared files or databases |
| 134 | +- Session maps are keyed by unique identifiers (e.g., Slack channel + thread timestamp) |
| 135 | +- Bounded collections with eviction to prevent memory leaks |
| 136 | + |
| 137 | +## Repository Structure |
| 138 | + |
| 139 | +``` |
| 140 | +kubeopencode-plugins/ |
| 141 | + AGENTS.md # This file |
| 142 | + opencode-slack-plugin/ # Slack Socket Mode integration |
| 143 | + src/index.ts # Plugin source |
| 144 | + package.json # npm package config |
| 145 | + tsconfig.json |
| 146 | + dist/ # Built output (tsup) |
| 147 | + README.md # Setup instructions |
| 148 | +``` |
| 149 | +
|
| 150 | +## Development |
| 151 | +
|
| 152 | +### Building a Plugin |
| 153 | +
|
| 154 | +```bash |
| 155 | +cd opencode-slack-plugin |
| 156 | +npm install |
| 157 | +npm run typecheck # tsc --noEmit |
| 158 | +npm run build # tsup -> dist/ |
| 159 | +``` |
| 160 | + |
| 161 | +### Testing Locally |
| 162 | + |
| 163 | +Copy the built plugin to your OpenCode plugins directory: |
| 164 | + |
| 165 | +```bash |
| 166 | +cp dist/index.js ~/.config/opencode/plugins/slack-plugin.js |
| 167 | +``` |
| 168 | + |
| 169 | +Or add to `opencode.json`: |
| 170 | + |
| 171 | +```json |
| 172 | +{ |
| 173 | + "plugin": ["file:///path/to/opencode-slack-plugin"] |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +### Testing with KubeOpenCode |
| 178 | + |
| 179 | +Create an Agent with the plugin: |
| 180 | + |
| 181 | +```yaml |
| 182 | +apiVersion: kubeopencode.io/v1alpha1 |
| 183 | +kind: Agent |
| 184 | +metadata: |
| 185 | + name: my-agent |
| 186 | +spec: |
| 187 | + plugins: |
| 188 | + - name: "opencode-slack-plugin" |
| 189 | + credentials: |
| 190 | + - secretRef: |
| 191 | + name: slack-credentials |
| 192 | + # ... |
| 193 | +``` |
| 194 | + |
| 195 | +## Style Guide |
| 196 | + |
| 197 | +- TypeScript, ESM (`"type": "module"`) |
| 198 | +- Use `@opencode-ai/plugin` as a peer dependency |
| 199 | +- Prefer `const` over `let`; use early returns over else blocks |
| 200 | +- Avoid `try/catch` when possible; use `.catch(() => {})` for best-effort operations |
| 201 | +- Keep each plugin in a single file unless complexity demands splitting |
| 202 | +- English comments only |
0 commit comments