| 1 | # Plugin bundles |
| 2 | |
| 3 | Codewhale supports a deliberately small plugin-bundle boundary. The boundary |
| 4 | was drawn in v0.9.1 and is extended deliberately in v0.9.10: a bundle may |
| 5 | contribute declarative Skills, MCP configuration, Commands, Agent profiles, |
| 6 | and Hooks through Codewhale's existing engines. Unsupported declarations stay |
| 7 | inventoried instead of disabling a mixed bundle. Discovery alone never |
| 8 | executes, enables, trusts, downloads, updates, or installs anything. |
| 9 | |
| 10 | This document owns the bundle formats, discovery, |
| 11 | validation, and the trust/enable/runtime contract. [PLUGINS.md](PLUGINS.md) |
| 12 | owns how bits get onto and off disk — the `/plugin install`, `update`, |
| 13 | `uninstall`, and `suggest` on-ramp added in v0.9.4 (#5182). Compatible Claude Code bundles use the same native adapters; their supported subset is |
| 14 | [CLAUDE_PLUGIN_COMPAT.md](CLAUDE_PLUGIN_COMPAT.md). |
| 15 | For a runnable native example and explicit OpenCode/DSH data conversion, see |
| 16 | [Write your first plugin](PLUGIN_AUTHORING.md). |
| 17 | |
| 18 | ## Discovery and precedence |
| 19 | |
| 20 | Codewhale scans only its own roots, looking in each `<name>/` directory for a |
| 21 | manifest named `plugin.json` (the native Agent Plugins v1.0.0 format, since |
| 22 | v0.9.4), `kimi.plugin.json` (the compatible Kimi Skills/MCP subset, since |
| 23 | v0.9.8), `plugin.toml` (the legacy Codewhale format, still fully readable), or |
| 24 | `.claude-plugin/plugin.json` (the compatible Claude subset, since v0.9.13): |
| 25 | |
| 26 | - User: `~/.codewhale/plugins/<name>/` |
| 27 | - Workspace: `<workspace>/.codewhale/plugins/<name>/` |
| 28 | |
| 29 | A bundle that publishes multiple formats is read through `plugin.json` first, |
| 30 | then `kimi.plugin.json`, then the legacy `plugin.toml`, then |
| 31 | `.claude-plugin/plugin.json`. |
| 32 | Computer Use ships as a built-in bundle; it still requires review and |
| 33 | enablement before activation. The internal precedence order is |
| 34 | built-in, user, then workspace; the first bundle with a given name wins. This |
| 35 | prevents a repository from shadowing an explicitly installed user bundle. |
| 36 | Symbolic-link roots, manifests, component paths, and nested component files |
| 37 | fail closed. |
| 38 | |
| 39 | The embedded Computer Use files are materialized under |
| 40 | `$CODEWHALE_HOME/builtin-plugins/snapshots/computer-use-<bundle-digest>/computer-use`. |
| 41 | Each process captures only its own embedded digest's discovery root. Concurrent |
| 42 | builds therefore keep separate, complete source trees; publishers never delete |
| 43 | or replace an existing snapshot. Reuse checks every embedded byte, directory |
| 44 | entry, file type, executable flag, and the stamp. A partial or altered snapshot |
| 45 | is rejected without repair. Interrupted private staging directories are not |
| 46 | discovered or reused. |
| 47 | |
| 48 | The existing path-bound plugin identity and trust rules apply: identical embedded |
| 49 | bytes at the same home reuse the same identity; changed bytes require a fresh |
| 50 | review and enablement. Moving from the older mutable |
| 51 | `builtin-plugins/computer-use` layout also requires one fresh review. Legacy |
| 52 | bundles and receipts remain intact for running older binaries; no trust is |
| 53 | migrated. Diagnostics do not create a missing Codewhale home. |
| 54 | The selected home may itself be a symlink: its resolved directory is pinned |
| 55 | before creating any built-in paths. Links in the owned built-in cache paths |
| 56 | still fail closed. |
| 57 | |
| 58 | New user and workspace bundles are always untrusted and disabled. Discovery is |
| 59 | read-only and does not inspect any other application's extension or credential |
| 60 | directories: ambient roots such as `.claude/plugins` or `.cursor/plugins` are |
| 61 | never scanned. |
| 62 | |
| 63 | Pre-v0.9.1 `overrides.json` enablement was intentionally not imported as |
| 64 | trust; every bundle activates only through the content-hash and |
| 65 | `codewhale-plugin-capabilities-v3` activation-policy review below. |
| 66 | |
| 67 | ## Manifest |
| 68 | |
| 69 | All supported encodings parse into the same internal manifest, so validation, hashing, |
| 70 | review, and runtime behavior are identical downstream. On-disk auto-migration |
| 71 | between them is deliberately not performed; `/plugin export <name> |
| 72 | <target-dir>` publishes a loaded bundle as a spec-valid Agent Plugins v1.0.0 |
| 73 | directory without modifying the installed one. |
| 74 | |
| 75 | ### `plugin.json` (Agent Plugins v1.0.0) |
| 76 | |
| 77 | The standard's manifest root is closed: `$schema`, `name`, and the optional |
| 78 | well-known fields (`version`, `description`, `author`, `homepage`, |
| 79 | `repository`, `license`, `keywords`, `extensions`). An unknown root key is a |
| 80 | parse error. Client-specific data lives under `extensions`, keyed by |
| 81 | reverse-domain namespace: unknown vendor namespaces are ignored, never |
| 82 | rejected — that is what lets a bundle authored for another client load here — |
| 83 | while unknown keys inside Codewhale's own `extensions["net.codewhale"]` |
| 84 | namespace are rejected rather than silently dropped. |
| 85 | |
| 86 | Names follow the standard's rule: 1–64 lowercase ASCII letters, digits, or |
| 87 | internal single `-`/`.`, starting and ending alphanumeric, never `--` or `..`. |
| 88 | A `skills/` directory in the bundle root is picked up automatically; other |
| 89 | component locations, `capabilities`, `when`, and `display_name` ride in |
| 90 | `extensions["net.codewhale"]`. |
| 91 | |
| 92 | MCP servers cannot live in `plugin.json` (the root is closed); they live in a |
| 93 | sibling `mcp.json` under `mcpServers`, with `stdio`, `streamable-http`, or |
| 94 | `sse` transports (`type` may be omitted and is inferred from `command` vs |
| 95 | `url`). Codewhale-only server options — timeouts, tool filters, env-backed |
| 96 | credentials, enablement — ride per-server under `extensions["net.codewhale"]`. |
| 97 | The `env` names `PLUGIN_ROOT` and `PLUGIN_DATA` are reserved by the standard |
| 98 | for the host runtime and are rejected in plugin definitions. |
| 99 | |
| 100 | ### `plugin.toml` (legacy Codewhale format) |
| 101 | |
| 102 | ```toml |
| 103 | schema_version = 1 |
| 104 | |
| 105 | [plugin] |
| 106 | name = "example" |
| 107 | version = "0.1.0" |
| 108 | description = "Example instruction and MCP bundle" |
| 109 | author = "Example Author" |
| 110 | |
| 111 | [skills] |
| 112 | path = "skills" |
| 113 | |
| 114 | [commands] |
| 115 | path = "commands" |
| 116 | |
| 117 | [agents] |
| 118 | path = "agents" |
| 119 | |
| 120 | [hooks] |
| 121 | path = "hooks" |
| 122 | |
| 123 | [mcp_servers.local] |
| 124 | command = "node" |
| 125 | args = ["server.js"] |
| 126 | cwd = "mcp" |
| 127 | |
| 128 | [mcp_servers.remote] |
| 129 | url = "https://example.invalid/mcp" |
| 130 | |
| 131 | [capabilities] |
| 132 | network_hosts = ["example.invalid"] |
| 133 | |
| 134 | [when] |
| 135 | os = ["macos", "linux", "windows"] |
| 136 | binaries = ["node"] |
| 137 | ``` |
| 138 | |
| 139 | Legacy TOML names are 1–64 lowercase ASCII letters, digits, or internal |
| 140 | hyphens. A pre-versioned manifest without `schema_version` still parses, with |
| 141 | a migration warning from `/plugin validate` (and `0.0.0` displayed when |
| 142 | `[plugin].version` is missing). An unknown top-level table or field is a |
| 143 | parse error (`deny_unknown_fields`), reported by byte offset without echoing |
| 144 | manifest values. |
| 145 | |
| 146 | ### Validation (both formats) |
| 147 | |
| 148 | Component paths must be relative, contained, present, and free of symbolic |
| 149 | links or Windows reparse points (including junctions and mount points). The v1 |
| 150 | schema rejects unknown MCP fields, ambiguous local/remote |
| 151 | transport combinations, unbounded lists/timeouts, and overlapping tool |
| 152 | filters. |
| 153 | |
| 154 | Remote MCP URLs must use HTTPS, except for explicit loopback HTTP endpoints. |
| 155 | They cannot contain user information, a query, or a fragment. Literal headers |
| 156 | are rejected: authentication must name a source environment variable through |
| 157 | `env_headers` or `bearer_token_env_var`. A remote bundle must declare exactly |
| 158 | the normalized host set used by its endpoints in |
| 159 | `capabilities.network_hosts`; endpoint scheme, normalized host, port, and path |
| 160 | remain bound to the review. Redirects are limited and must retain that exact |
| 161 | normalized origin. Reviewed remote transports use an explicit no-proxy HTTP |
| 162 | client: plugin bundles never read or use ambient `HTTP_PROXY`, `HTTPS_PROXY`, |
| 163 | or `NO_PROXY` values, because proxy credentials and proxy observation are |
| 164 | outside the reviewed authority. User-authored MCP configuration keeps its |
| 165 | existing explicit proxy support. |
| 166 | |
| 167 | Local stdio environment entries must use exact `${SOURCE_ENV}` references. |
| 168 | The review shows destination and source names, but never reads or prints their |
| 169 | values. Plugin children inherit only Codewhale's base secret-scrubbed child |
| 170 | environment plus those reviewed mappings; credential-capable proxy variables |
| 171 | and the broader compatibility environment used by user-authored MCP |
| 172 | configuration are not inherited ambiently. Absolute arguments and parent |
| 173 | traversal are rejected; contained bundle entrypoints are frozen to their |
| 174 | staged paths before spawn. |
| 175 | |
| 176 | Every stdio argument is shown losslessly as a JSON string during review. |
| 177 | Common credential-bearing flags and known literal token shapes are rejected |
| 178 | from argv; credentials must instead use a reviewed environment mapping. |
| 179 | Plugin-contributed MCP OAuth has been disabled since v0.9.1 and remains |
| 180 | disabled as of v0.9.6, including discovery, login, refresh, and token storage; |
| 181 | a manifest declaring OAuth fields on a plugin MCP server fails validation. |
| 182 | |
| 183 | ### Active and inactive component surfaces |
| 184 | |
| 185 | Codewhale 0.9.10 activates declarative `[skills]`, `[mcp_servers.*]`, |
| 186 | `[commands]`, `[agents]`, and `[hooks]` components from its content-addressed |
| 187 | runtime snapshot. Commands use markdown command files, Agents use Fleet TOML |
| 188 | profiles, and Hooks use `HooksConfig` TOML files. A component may name one file |
| 189 | or a directory of the corresponding files. Ordinary user/workspace commands |
| 190 | and Agent profiles keep precedence over plugin contributions; trusted project |
| 191 | hooks run after plugin hooks. |
| 192 | |
| 193 | The manifest can additionally inventory the following inactive surfaces. |
| 194 | Those declarations stay hashed, reviewed, and displayed, but do not activate |
| 195 | and no longer disable the whole bundle: |
| 196 | |
| 197 | ```toml |
| 198 | [lsp] # TOML alias: [lsp_servers] |
| 199 | path = "lsp" |
| 200 | |
| 201 | [native] # TOML alias: [native_extension] |
| 202 | path = "native" |
| 203 | |
| 204 | [capabilities] |
| 205 | filesystem_roots = ["workspace"] |
| 206 | network_hosts = ["api.example.invalid"] |
| 207 | lifecycle_mutation = true |
| 208 | ``` |
| 209 | |
| 210 | (In a `plugin.json` bundle the same tables ride under |
| 211 | `extensions["net.codewhale"]`.) |
| 212 | |
| 213 | The accept/reject behavior is deliberately loud, never silent: |
| 214 | |
| 215 | - Compatibility is per-component: `full` when every declared surface has an |
| 216 | adapter (or the bundle is empty), `partial` when supported components can |
| 217 | activate beside named inactive surfaces, and `unsupported` when the bundle |
| 218 | only declares surfaces Codewhale cannot activate yet. The same versioned |
| 219 | activation policy (v3) drives those labels, the runtime adapters, and the |
| 220 | capability hash. A future Codewhale that starts executing LSP or native code |
| 221 | must change that policy, which changes the capability hash and forces |
| 222 | re-review. v1 and v2 trust receipts fail closed as |
| 223 | `capabilities-changed`. |
| 224 | - A **recognized-but-inactive** declaration (`lsp`, `native`, a non-empty |
| 225 | `capabilities.filesystem_roots`, or |
| 226 | `capabilities.lifecycle_mutation = true`) parses and is validated like any |
| 227 | component (contained, present, link-free). It is counted in the inventory, |
| 228 | hashed into the capability receipt, shown in review and `/plugin show` as |
| 229 | inactive, and never executed. A reviewed, trusted, applicable mixed bundle |
| 230 | can still be enabled: supported declarative components become active, and |
| 231 | the inactive surfaces stay named as inactive. |
| 232 | - An **all-unsupported** bundle can be reviewed and trusted, but `/plugin |
| 233 | enable` fails closed and names the inactive surfaces. There is nothing |
| 234 | Codewhale can honestly activate. |
| 235 | - An **unrecognized** section or field is a validation failure, not an |
| 236 | inventory entry: unknown top-level TOML tables, unknown MCP server fields, |
| 237 | unknown `plugin.json` root keys, and unknown keys inside |
| 238 | `extensions["net.codewhale"]` are all rejected outright. The single |
| 239 | ignore-without-error case is another vendor's `extensions` namespace in the |
| 240 | Agent Plugins format, which the standard requires clients to skip. |
| 241 | - `capabilities.network_hosts` is not a future surface: it is enforced today, |
| 242 | and must exactly match the normalized host set of the bundle's remote MCP |
| 243 | endpoints (so it cannot be declared without them, or omitted with them). |
| 244 | |
| 245 | A successful environment or health check is never treated as trust. |
| 246 | |
| 247 | ## Review, trust, and enablement |
| 248 | |
| 249 | Use the in-session command surface: |
| 250 | |
| 251 | ```text |
| 252 | /plugin list |
| 253 | /plugin validate example |
| 254 | /plugin show example |
| 255 | /plugin enable example |
| 256 | ``` |
| 257 | |
| 258 | The first `enable` opens a review showing source, component inventory, |
| 259 | requested permissions, sanitized MCP endpoints, full content and capability |
| 260 | hashes, and inactive declarations. It also prints an exact confirmation: |
| 261 | |
| 262 | ```text |
| 263 | /plugin trust example <full-content-sha256>.<full-capability-sha256> |
| 264 | ``` |
| 265 | |
| 266 | Run that exact command only after reviewing the bundle. The confirmation token |
| 267 | uses both complete SHA-256 receipts rather than display prefixes. The |
| 268 | capability receipt is the v3 digest: it still hashes the complete inventory |
| 269 | and also binds this build's activation policy (which adapters are executable |
| 270 | versus inventoried-only). Trust first |
| 271 | copies the complete reviewed tree into a Codewhale-owned, content-addressed |
| 272 | runtime snapshot and records the matching receipt; it does not activate |
| 273 | anything. |
| 274 | Then run `/plugin enable example` again. Trust and enablement are separate: |
| 275 | |
| 276 | - `/plugin disable example` stops contribution while preserving trust. |
| 277 | - `/plugin revoke example` removes trust while preserving the enablement bit; |
| 278 | the bundle remains inactive until reviewed again. |
| 279 | - `/plugin reload` rebuilds the current workspace registry when files have |
| 280 | changed on disk. |
| 281 | |
| 282 | (`/plugin install`, `update`, and `uninstall` place, replace, and remove the |
| 283 | bits themselves and always drop into this same review — see |
| 284 | [PLUGINS.md](PLUGINS.md). `/plugin suggest` ranks installed bundles and |
| 285 | any locally added marketplace catalogs; sending a matching task can toast the |
| 286 | same next step without installing anything, and a live composer CTA plus an |
| 287 | append-only `<recommended_plugins>` user block offer the same review path.) |
| 288 | |
| 289 | Trust, enable, disable, revoke, and reload rebuild the current workspace's |
| 290 | Skills, MCP, Commands, Agent profiles, and Hooks immediately. Each persisted |
| 291 | transition advances a per-bundle generation under a stable cross-process lock. |
| 292 | A generation change cancels in-flight MCP work, removes cached catalog |
| 293 | entries, terminates an idle plugin stdio child, and denies persisted queued |
| 294 | Skills carrying the older authority receipt. |
| 295 | |
| 296 | The review distinguishes remote MCP endpoints from local stdio MCP servers. |
| 297 | A local stdio server is a child process running with the Codewhale user's host |
| 298 | filesystem and network authority; plugin trust is not an OS sandbox. The |
| 299 | review therefore shows the command, argument count, working directory, |
| 300 | environment-variable names, and this host-authority warning without printing |
| 301 | environment or header values. MCP tool approval still applies after the |
| 302 | server starts. |
| 303 | |
| 304 | Trust receipts live in `~/.codewhale/plugins/state.json`. Atomic owner-only |
| 305 | writes record the full content hash, capability hash, reviewed capability |
| 306 | inventory, generation, and review time, with the latest 32 reviews retained as |
| 307 | a bounded audit trail. Malformed or unsupported state is not overwritten: all |
| 308 | bundles fail closed until the state file is repaired or moved. |
| 309 | |
| 310 | The content hash covers the manifest, complete bundle tree, and executable |
| 311 | shape in deterministic path order, including local MCP entrypoints and |
| 312 | companion assets. Staging is bounded, rejects symbolic links and unsupported |
| 313 | file kinds (plus every Windows reparse point and hard-linked files), uses an |
| 314 | atomic destination swap, and applies owner-only runtime permissions or ACLs |
| 315 | through validated object handles on Windows. The capability hash covers the |
| 316 | normalized component and permission inventory. A source or staged-content |
| 317 | edit, capability change, or unsafe runtime-root replacement invalidates the |
| 318 | receipt deterministically; an already-enabled bundle becomes inactive until |
| 319 | it is reviewed again. This is the same invalidation `/plugin update` relies |
| 320 | on: replaced bytes stop matching the receipt, forcing re-review. |
| 321 | |
| 322 | ## Runtime behavior |
| 323 | |
| 324 | An active bundle must be enabled, trusted for its current hashes, applicable to |
| 325 | the host, and free of validation errors. A reviewed mixed bundle may be |
| 326 | active, but only supported components in the reviewed v3 activation mask are |
| 327 | consumable. Unsupported components remain listed, hashed, reviewed, and |
| 328 | inactive. |
| 329 | |
| 330 | - Skills are exposed only as `<plugin>:<skill>`. The model-facing catalogue and |
| 331 | `load_skill` use an in-memory snapshot bound to the reviewed staged tree, |
| 332 | rather than reading a mutable source path at execution time. `load_skill` |
| 333 | revalidates source, stage, receipt, workspace, and generation immediately |
| 334 | before releasing content and fails closed on drift. Queued messages persist |
| 335 | the same provenance and repeat that check at dispatch. `/skills inspect` |
| 336 | identifies the reviewed bundle without exposing its mutable source path. |
| 337 | - MCP server names are exposed as |
| 338 | `plugin-<plugin-name-byte-length>-<plugin>-<server>` so hyphens in either |
| 339 | component cannot create an authority collision. Disabled or untrusted |
| 340 | bundles are denied again at the headless MCP adapter. Authority is checked |
| 341 | before connection, immediately before every lazy stdio spawn, after |
| 342 | transport construction, and before each tool/resource/prompt operation. |
| 343 | Persisted generation/enablement/trust state is also watched while an |
| 344 | operation is in flight, so disable, revoke, or another cross-process state |
| 345 | transition cancels the operation and terminates a plugin stdio child. Full |
| 346 | source and staged-tree hashes are revalidated at dispatch/catalogue |
| 347 | boundaries; the runtime does not continuously re-hash those trees during an |
| 348 | already-running MCP call. Source or stage drift therefore fails the next |
| 349 | boundary and drops the stale connection/catalogue entry, but is not claimed |
| 350 | to interrupt a call already executing. Every failure includes instructions |
| 351 | to reload, review, trust, and enable the bundle again. |
| 352 | - Commands load after ordinary user/workspace commands and saved workflows, so |
| 353 | existing definitions keep precedence and collisions are visible. The |
| 354 | palette hides a revoked command immediately; dispatch rechecks the full |
| 355 | receipt before expanding its body and reports a visible denial on stale |
| 356 | input. |
| 357 | - Agent profiles join the Fleet roster below explicit config, personal, and |
| 358 | workspace profiles but above built-ins. Roster collisions retain the |
| 359 | existing visible shadow record. Every Agent spawn rebuilds from the current |
| 360 | registry and rechecks the selected plugin profile's authority before its |
| 361 | prompt or route can be used. |
| 362 | - Hooks merge after global hooks and before trusted project hooks. Foreground |
| 363 | Hooks recheck authority immediately before process spawn; background Hooks |
| 364 | check before enqueue and again at dequeue so a queued, revoked Hook cannot |
| 365 | start later. |
| 366 | - Plain launch, resume, fork, exec, and serve each construct an immutable |
| 367 | workspace-scoped registry before constructing their plugin-backed catalogues. |
| 368 | - Constitution, repository instructions, permission rules, sandbox policy, |
| 369 | and MCP tool approval continue to outrank plugin instructions. |
| 370 | |
| 371 | `/plugin list`, `show`, `suggest`, and `validate` perform no network requests, |
| 372 | process launches, credential reads, or configuration writes. Reviews render |
| 373 | structural argv as lossless JSON strings and environment provenance without |
| 374 | values. Credential-bearing argv is rejected at manifest validation; |
| 375 | plugin-originated errors suppress URL query, authentication, argv, and |
| 376 | environment material. Legacy executable tools under `[tools].plugin_dir` |
| 377 | remain a distinct system and are listed under `/plugin tools`. |
| 378 | |
| 379 | ## Explicit non-goals as of v0.9.10 |
| 380 | |
| 381 | Federated marketplace catalogs (`/plugin marketplace add|list|show|remove|install`) |
| 382 | parse local Kimi-, Claude-, Codex-, and Codewhale-format catalog documents; see |
| 383 | the marketplace section below (`/plugin install` fetches |
| 384 | one reviewed source, and `/plugin suggest` ranks only what is already |
| 385 | installed), no ambient compatibility discovery, no automatic trust, no |
| 386 | plugin-contributed MCP OAuth, no LSP adapter, native extension runtime, or MCP |
| 387 | subscription adapter, no |
| 388 | foreign executable plugin runtime import, and no on-disk auto-migration of a |
| 389 | legacy `plugin.toml` to `plugin.json`. The explicit offline |
| 390 | [OpenCode/DSH converter](PLUGIN_AUTHORING.md#convert-an-existing-plugin) supports |
| 391 | selected portable Skills, static Streamable HTTP MCP declarations, and |
| 392 | explicitly packaged Node `.mjs`, `.js`, or `.cjs` MCP servers selected with `--stdio-root`. |
| 393 | Local source and dependencies are copied for the same native installation, |
| 394 | capability review, hash-bound trust and enable flow; conversion executes no |
| 395 | code or package manager. It does not migrate arbitrary bundles or reproduce |
| 396 | another client's runtime or policy. |
| 397 | The other capabilities above remain later work rather than implied support. |
| 398 | |
| 399 | ## Marketplace catalogs (#5311) |
| 400 | |
| 401 | `/plugin marketplace` reads LOCAL catalog documents in the real published |
| 402 | schemas (Kimi, Claude, Codex, Codewhale native; Codex via its policy markers) |
| 403 | and renders every candidate with an honest install plan: |
| 404 | |
| 405 | ```text |
| 406 | /plugin marketplace add <name> <path> # parse a local catalog file (no network) |
| 407 | /plugin marketplace list # catalogs + candidates + diagnostics |
| 408 | /plugin marketplace show <name> # one catalog in detail |
| 409 | /plugin marketplace remove <name> # forget a catalog (plugins unaffected) |
| 410 | /plugin marketplace install <catalog> <candidate> |
| 411 | ``` |
| 412 | |
| 413 | - `add` never fetches anything: it reads one local JSON file (≤4 MiB, regular |
| 414 | files only, symlinks refused) and stores the parsed catalog next to the |
| 415 | plugin state file. |
| 416 | - Catalog tiers and provenance (`official`, `curated`, …) are **display |
| 417 | only** — they never grant trust, enablement, or installation. |
| 418 | - Foreign policies are visibly ignored: a Codex `INSTALLED_BY_DEFAULT` entry |
| 419 | is listed with a `NO_AUTO_INSTALL` warning and nothing is installed until |
| 420 | an operator runs the install verb. |
| 421 | - Sources Codewhale cannot fetch (npm packages, `command:` sources, non-tarball |
| 422 | URLs) are listed as `not installable` with the reason. |
| 423 | - `install` routes through the same reviewed installer as `/plugin install`: |
| 424 | the bundle lands disabled and untrusted, and enters the hash-bound trust |
| 425 | review before anything activates. |
| 426 |