| 1 | # Plugin bundles |
| 2 | |
| 3 | Codewhale v0.9.1 supports a deliberately small plugin-bundle boundary. A |
| 4 | bundle may contribute declarative Skills and MCP server configuration through |
| 5 | Codewhale's existing engines. Discovery alone never executes, enables, trusts, |
| 6 | downloads, updates, or installs anything. For the `/plugin install` on-ramp |
| 7 | that places bundles onto disk, see [PLUGINS.md](PLUGINS.md). |
| 8 | |
| 9 | ## Discovery and precedence |
| 10 | |
| 11 | Codewhale scans only its own roots: |
| 12 | |
| 13 | - User: `~/.codewhale/plugins/<name>/plugin.toml` |
| 14 | - Workspace: `<workspace>/.codewhale/plugins/<name>/plugin.toml` |
| 15 | |
| 16 | No built-in bundle ships in v0.9.1. The internal precedence order is built-in, |
| 17 | user, then workspace; the first bundle with a given name wins. This prevents a |
| 18 | repository from shadowing an explicitly installed user bundle. Symbolic-link |
| 19 | roots, manifests, component paths, and nested component files fail closed. |
| 20 | |
| 21 | New user and workspace bundles are always untrusted and disabled. Discovery is |
| 22 | read-only and does not inspect any other application's extension or credential |
| 23 | directories. |
| 24 | |
| 25 | Pre-v0.9.1 `overrides.json` enablement is intentionally not imported as trust. |
| 26 | Existing bundles therefore return to disabled until they receive the v1 |
| 27 | content and capability review. |
| 28 | |
| 29 | ## Manifest |
| 30 | |
| 31 | Every bundle uses a versioned `plugin.toml` and a semantic version: |
| 32 | |
| 33 | ```toml |
| 34 | schema_version = 1 |
| 35 | |
| 36 | [plugin] |
| 37 | name = "example" |
| 38 | version = "0.1.0" |
| 39 | description = "Example instruction and MCP bundle" |
| 40 | author = "Example Author" |
| 41 | |
| 42 | [skills] |
| 43 | path = "skills" |
| 44 | |
| 45 | [mcp_servers.local] |
| 46 | command = "node" |
| 47 | args = ["server.js"] |
| 48 | cwd = "mcp" |
| 49 | |
| 50 | [mcp_servers.remote] |
| 51 | url = "https://example.invalid/mcp" |
| 52 | |
| 53 | [capabilities] |
| 54 | network_hosts = ["example.invalid"] |
| 55 | |
| 56 | [when] |
| 57 | os = ["macos", "linux", "windows"] |
| 58 | binaries = ["node"] |
| 59 | ``` |
| 60 | |
| 61 | Component paths must be relative, contained, present, and free of symbolic |
| 62 | links or Windows reparse points (including junctions and mount points). The v1 |
| 63 | schema rejects unknown MCP fields, ambiguous local/remote |
| 64 | transport combinations, unbounded lists/timeouts, and overlapping tool |
| 65 | filters. |
| 66 | |
| 67 | Remote MCP URLs must use HTTPS, except for explicit loopback HTTP endpoints. |
| 68 | They cannot contain user information, a query, or a fragment. Literal headers |
| 69 | are rejected: authentication must name a source environment variable through |
| 70 | `env_headers` or `bearer_token_env_var`. A remote bundle must declare exactly |
| 71 | the normalized host set used by its endpoints in |
| 72 | `capabilities.network_hosts`; endpoint scheme, normalized host, port, and path |
| 73 | remain bound to the review. Redirects are limited and must retain that exact |
| 74 | normalized origin. Reviewed remote transports use an explicit no-proxy HTTP |
| 75 | client: v1 bundles never read or use ambient `HTTP_PROXY`, `HTTPS_PROXY`, or |
| 76 | `NO_PROXY` values, because proxy credentials and proxy observation are outside |
| 77 | the reviewed authority. User-authored MCP configuration keeps its existing |
| 78 | explicit proxy support. |
| 79 | |
| 80 | Local stdio environment entries must use exact `${SOURCE_ENV}` references. |
| 81 | The review shows destination and source names, but never reads or prints their |
| 82 | values. Plugin children inherit only Codewhale's base secret-scrubbed child |
| 83 | environment plus those reviewed mappings; credential-capable proxy variables |
| 84 | and the broader compatibility environment used by user-authored MCP |
| 85 | configuration are not inherited ambiently. Absolute arguments and parent |
| 86 | traversal are rejected; contained bundle entrypoints are frozen to their |
| 87 | staged paths before spawn. |
| 88 | |
| 89 | Every stdio argument is shown losslessly as a JSON string during review. |
| 90 | Common credential-bearing flags and known literal token shapes are rejected |
| 91 | from argv; credentials must instead use a reviewed environment mapping. |
| 92 | Plugin-contributed MCP OAuth is disabled for v0.9.1, including discovery, |
| 93 | login, refresh, and token storage. |
| 94 | |
| 95 | `[skills]` and `[mcp_servers.*]` are the only active component adapters in |
| 96 | v0.9.1. The manifest can inventory the following future surfaces, but a bundle |
| 97 | declaring any of them cannot be enabled yet: |
| 98 | |
| 99 | ```toml |
| 100 | [commands] |
| 101 | path = "commands" |
| 102 | |
| 103 | [agents] |
| 104 | path = "agents" |
| 105 | |
| 106 | [hooks] |
| 107 | path = "hooks" |
| 108 | |
| 109 | [lsp] |
| 110 | path = "lsp" |
| 111 | |
| 112 | [native] |
| 113 | path = "native" |
| 114 | |
| 115 | [capabilities] |
| 116 | filesystem_roots = ["workspace"] |
| 117 | network_hosts = ["api.example.invalid"] |
| 118 | lifecycle_mutation = true |
| 119 | ``` |
| 120 | |
| 121 | Remote MCP endpoint hosts must exactly match the displayed network inventory. |
| 122 | A successful environment or health check is never treated as trust. |
| 123 | |
| 124 | ## Review, trust, and enablement |
| 125 | |
| 126 | Use the in-session command surface: |
| 127 | |
| 128 | ```text |
| 129 | /plugin list |
| 130 | /plugin validate example |
| 131 | /plugin show example |
| 132 | /plugin enable example |
| 133 | ``` |
| 134 | |
| 135 | The first `enable` opens a review showing source, component inventory, |
| 136 | requested permissions, sanitized MCP endpoints, full content and capability |
| 137 | hashes, and inactive declarations. It also prints an exact confirmation: |
| 138 | |
| 139 | ```text |
| 140 | /plugin trust example <full-content-sha256>.<full-capability-sha256> |
| 141 | ``` |
| 142 | |
| 143 | Run that exact command only after reviewing the bundle. The confirmation token |
| 144 | uses both complete SHA-256 receipts rather than display prefixes. Trust first |
| 145 | copies the complete reviewed tree into a Codewhale-owned, content-addressed |
| 146 | runtime snapshot and records the matching receipt; it does not activate |
| 147 | anything. |
| 148 | Then run `/plugin enable example` again. Trust and enablement are separate: |
| 149 | |
| 150 | - `/plugin disable example` stops contribution while preserving trust. |
| 151 | - `/plugin revoke example` removes trust while preserving the enablement bit; |
| 152 | the bundle remains inactive until reviewed again. |
| 153 | - `/plugin reload` rebuilds the current workspace registry when files have |
| 154 | changed on disk. |
| 155 | |
| 156 | Trust, enable, disable, revoke, and reload rebuild the current workspace's |
| 157 | Skill catalogue and MCP pool immediately. Each persisted transition advances a |
| 158 | per-bundle generation under a stable cross-process lock. A generation change |
| 159 | cancels in-flight MCP work, removes cached catalog entries, terminates an idle |
| 160 | plugin stdio child, and denies persisted queued Skills carrying the older |
| 161 | authority receipt. |
| 162 | |
| 163 | The review distinguishes remote MCP endpoints from local stdio MCP servers. |
| 164 | A local stdio server is a child process running with the Codewhale user's host |
| 165 | filesystem and network authority; plugin trust is not an OS sandbox. The |
| 166 | review therefore shows the command, argument count, working directory, |
| 167 | environment-variable names, and this host-authority warning without printing |
| 168 | environment or header values. MCP tool approval still applies after the |
| 169 | server starts. |
| 170 | |
| 171 | Trust receipts live in `~/.codewhale/plugins/state.json`. Atomic owner-only |
| 172 | writes record the full content hash, capability hash, reviewed capability |
| 173 | inventory, generation, and review time, with the latest 32 reviews retained as |
| 174 | a bounded audit trail. Malformed or unsupported state is not overwritten: all |
| 175 | bundles fail closed until the state file is repaired or moved. |
| 176 | |
| 177 | The content hash covers the manifest, complete bundle tree, and executable |
| 178 | shape in deterministic path order, including local MCP entrypoints and |
| 179 | companion assets. Staging is bounded, rejects symbolic links and unsupported |
| 180 | file kinds (plus every Windows reparse point and hard-linked files), uses an |
| 181 | atomic destination swap, and applies owner-only runtime permissions or ACLs |
| 182 | through validated object handles on Windows. The capability hash covers the |
| 183 | normalized component and permission inventory. A source or staged-content |
| 184 | edit, capability change, or unsafe runtime-root replacement invalidates the |
| 185 | receipt deterministically; an already-enabled bundle becomes inactive until |
| 186 | it is reviewed again. |
| 187 | |
| 188 | ## Runtime behavior |
| 189 | |
| 190 | An active bundle must be enabled, trusted for its current hashes, applicable to |
| 191 | the host, free of validation errors, and limited to supported component kinds. |
| 192 | |
| 193 | - Skills are exposed only as `<plugin>:<skill>`. The model-facing catalogue and |
| 194 | `load_skill` use an in-memory snapshot bound to the reviewed staged tree, |
| 195 | rather than reading a mutable source path at execution time. `load_skill` |
| 196 | revalidates source, stage, receipt, workspace, and generation immediately |
| 197 | before releasing content and fails closed on drift. Queued messages persist |
| 198 | the same provenance and repeat that check at dispatch. `/skills inspect` |
| 199 | identifies the reviewed bundle without exposing its mutable source path. |
| 200 | - MCP server names are exposed as |
| 201 | `plugin-<plugin-name-byte-length>-<plugin>-<server>` so hyphens in either |
| 202 | component cannot create an authority collision. Disabled or untrusted |
| 203 | bundles are denied again at the headless MCP adapter. Authority is checked |
| 204 | before connection, immediately before every lazy stdio spawn, after |
| 205 | transport construction, and before each tool/resource/prompt operation. |
| 206 | Persisted generation/enablement/trust state is also watched while an |
| 207 | operation is in flight, so disable, revoke, or another cross-process state |
| 208 | transition cancels the operation and terminates a plugin stdio child. Full |
| 209 | source and staged-tree hashes are revalidated at dispatch/catalogue |
| 210 | boundaries; v0.9.1 does not continuously re-hash those trees during an |
| 211 | already-running MCP call. Source or stage drift therefore fails the next |
| 212 | boundary and drops the stale connection/catalogue entry, but is not claimed |
| 213 | to interrupt a call already executing. Every failure includes instructions |
| 214 | to reload, review, trust, and enable the bundle again. |
| 215 | - Plain launch, resume, fork, exec, and serve each construct an immutable |
| 216 | workspace-scoped registry before constructing their Skill or MCP catalogue. |
| 217 | - Constitution, repository instructions, permission rules, sandbox policy, |
| 218 | and MCP tool approval continue to outrank plugin instructions. |
| 219 | |
| 220 | `/plugin list`, `show`, and `validate` perform no network requests, process |
| 221 | launches, credential reads, or configuration writes. Reviews render structural |
| 222 | argv as lossless JSON strings and environment provenance without values. |
| 223 | Credential-bearing argv is rejected at manifest validation; plugin-originated |
| 224 | errors suppress URL query, authentication, argv, and environment material. |
| 225 | Legacy executable tools under `[tools].plugin_dir` remain a distinct system |
| 226 | and are listed under `/plugin tools`. |
| 227 | |
| 228 | ## Explicit non-goals for v0.9.1 |
| 229 | |
| 230 | There is no remote marketplace, install/update command, ambient compatibility |
| 231 | discovery, automatic trust, hook adapter, command adapter, agent adapter, LSP |
| 232 | adapter, native extension runtime, MCP subscription adapter, or migration of |
| 233 | another application's bundle. These remain later work rather than implied |
| 234 | capabilities. |
| 235 |