customizations: redesign as a typed two-level tree#152
Merged
Conversation
Replace the opaque, plugin-only customization model with a discriminated union that distinguishes containers from the components inside them. This addresses three concrete problems with the previous design. 1. Constituent parts were a one-off. The old model bolted an `agents` array on the side of each customization to expose contributed agents; skills, prompts, rules, hooks, and MCP servers had no equivalent. Agents are no longer special — agent hosts publish every contributed component (under either a plugin or a directory) as a typed child of its container. 2. Non-plugin customizations didn't fit. The protocol assumed every customization was an Open Plugins package, so workspace sources like `.github/skills/` had nowhere to live. Top-level entries are now Plugin or Directory. A Directory is just a folder the host watches, which is how a host can surface things like a host-global instructions folder or workspace skills without pretending they're plugins. Clients can tell a directory's "scope" from whether its URI sits inside the workspace, so no separate scope flag is added. 3. Saving customizations had no shape. A writable Directory doubles as a save target: clients persist new customizations with the existing `resourceWrite` command, and the host re-emits the directory with updated children. Clients continue to publish customizations in Open Plugins format (ClientPluginCustomization, with an opaque nonce). They don't know how a given harness consumes plugins, so the host is free to re-interpret what they publish. The new shape: - Containers (Plugin / Directory) carry id, uri, name, load state, optional clientId, and a children array. - Children (Agent, Skill, Prompt, Rule, Hook, McpServer) are typed leaves with optional metadata sourced from the Open Plugins component spec (descriptions, globs, hook events, etc.). Host-internal execution details (commands, env, args) stay off the wire. - `id` is the protocol handle for every action; `uri` is the source location. Inline declarations (e.g. an MCP server embedded in a plugins.json manifest) use the containing file's uri plus a new optional range. Actions collapse to four: customizationsChanged (full replace), customizationToggled (by id, walks both levels, client-dispatchable), customizationUpdated (upsert one container, children included), and customizationRemoved (by id, cascades). Children are always updated through their container. Touches the type definitions, the session reducer and its fixtures, the version registry, the Swift and Rust generators (plus the hand-written reducer branches in each), and the customizations and actions guides.
DonJayamanne
previously approved these changes
May 27, 2026
TylerLeonhardt
previously approved these changes
May 27, 2026
roblourens
approved these changes
May 27, 2026
connor4312
pushed a commit
that referenced
this pull request
May 28, 2026
The Deploy Documentation workflow has been failing since PR #152 because two markdown files reference `/reference/commands#resourcewrite`, which is not a real page in the generated reference. The `resourceWrite` command is defined on the `common` channel (`docs/reference/common.md`), so the correct link is `/reference/common#resourcewrite`. Updated the JSDoc on `DirectoryCustomization` in `types/channels-session/state.ts` (which propagates to the generated schema and Rust outputs) and the guide page `docs/guide/customizations.md`, then re-ran `npm run generate` to refresh derived artifacts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replace the opaque, plugin-only customization model with a discriminated union that distinguishes containers from the components inside them. This addresses three concrete problems with the previous design.
Constituent parts were a one-off. The old model bolted an
agentsarray on the side of each customization to expose contributed agents; skills, prompts, rules, hooks, and MCP servers had no equivalent. Agents are no longer special — agent hosts publish every contributed component (under either a plugin or a directory) as a typed child of its container.Non-plugin customizations didn't fit. The protocol assumed every customization was an Open Plugins package, so workspace sources like
.github/skills/had nowhere to live. Top-level entries are now Plugin or Directory. A Directory is just a folder the host watches, which is how a host can surface things like a host-global instructions folder or workspace skills without pretending they're plugins. Clients can tell a directory's "scope" from whether its URI sits inside the workspace, so no separate scope flag is added.Saving customizations had no shape. A writable Directory doubles as a save target: clients persist new customizations with the existing
resourceWritecommand, and the host re-emits the directory with updated children.Clients continue to publish customizations in Open Plugins format (ClientPluginCustomization, with an opaque nonce). They don't know how a given harness consumes plugins, so the host is free to re-interpret what they publish.
The new shape:
idis the protocol handle for every action;uriis the source location. Inline declarations (e.g. an MCP server embedded in a plugins.json manifest) use the containing file's uri plus a new optional range.Actions collapse to four: customizationsChanged (full replace), customizationToggled (by id, walks both levels, client-dispatchable), customizationUpdated (upsert one container, children included), and customizationRemoved (by id, cascades). Children are always updated through their container.
Touches the type definitions, the session reducer and its fixtures, the version registry, the Swift and Rust generators (plus the hand-written reducer branches in each), and the customizations and actions guides.