Skip to content

session: scoped customization enablement - #397

Merged
Connor Peet (connor4312) merged 4 commits into
mainfrom
session/scoped-customization-enablement
Aug 13, 2026
Merged

session: scoped customization enablement#397
Connor Peet (connor4312) merged 4 commits into
mainfrom
session/scoped-customization-enablement

Conversation

@connor4312

@connor4312 Connor Peet (connor4312) commented Aug 13, 2026

Copy link
Copy Markdown
Member

Note: I'm putting this up for review tonight, but will refine it more a bit tomorrow -- landing it to avoid conflicts with ongoing vscode dev

Summary

  • Adds scoped CustomizationEnablement decisions for plugins and MCP servers, with precedence ordered Session, Workspace, then Global.
  • Adds client-published child enablement and MCP client-bundled metadata; directories and leaf customizations retain their plain enabled semantics.
  • Ports reducer behavior and shared fixtures across all generated clients.

Wire contract

Producers publish enablement entries in descending specificity. Consumers may use enablement?.[0]?.enabled ?? true; absent or empty enablement means enabled. The host emits at most one Workspace decision for the session primary working directory.

Consumer: microsoft/vscode#330566

Customizations gain an optional `enablement` array of explicit decisions, one
per scope that has one:

    CustomizationEnablement =
      | { kind: 'global'; enabled: boolean }
      | { kind: 'workspace'; uri: URI; enabled: boolean }
      | { kind: 'session'; enabled: boolean }

The array is a wire contract. Producers MUST publish entries sorted by
descending specificity (session, workspace, then global), and the agent host
emits at most one workspace entry, for the session's primary working directory.
Consumers MAY therefore treat `enablement[0]` as decisive, with
`enablement?.[0]?.enabled ?? true` as the effective value. An absent or empty
array means no explicit decision, so the customization is enabled by default.
Only the host publishes this; clients treat it as read-only provenance.

The field lives on the customization base rather than on MCP servers alone, so
it applies to every customization type.

`session/customizationToggled` carries `enablement` in place of `enabled` and
replaces the complete decision set, so a caller changing one scope must include
every decision it intends to preserve. An empty array clears all decisions and
restores the default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@connor4312
Connor Peet (connor4312) enabled auto-merge (squash) August 13, 2026 06:12
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The wider `CustomizationEnablement` import and the extra `apply_toggle`
parameter push both past rustfmt's line width, so the generated output
needed a reflow. Caught by CI's "Check Rust formatting" step, which runs
`cargo fmt --check` separately from `cargo test`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR evolves the session customization model to support scoped enablement decisions (Session > Workspace > Global) for plugins and MCP servers, replacing the previous single enabled boolean on those customization kinds while keeping directories and leaf children on the legacy enabled semantics. It also ports the updated reducer behavior and fixtures across all generated clients and refreshes the JSON schemas and docs accordingly.

Changes:

  • Introduces CustomizationEnablement (scoped decisions) and updates PluginCustomization / McpServerCustomization to carry enablement? instead of enabled.
  • Updates session/customizationToggled to replace the complete enablement decision set (and updates reducers + fixtures accordingly).
  • Extends client-published plugin metadata (childEnablement) and MCP server metadata (isClientBundled), and propagates codegen/schema/doc updates across the repo.
Show a summary per file
File Description
types/channels-session/state.ts Adds scoped enablement types; updates customization shapes (plugins/MCP servers now use enablement?).
types/channels-session/actions.ts Updates session/customizationToggled payload from enabled: boolean to enablement: CustomizationEnablement[].
types/channels-session/reducer.ts Applies enablement changes in the TS reducer, preserving legacy enabled behavior for directories/leaf children.
schema/state.schema.json Updates JSON schema to match new enablement model and adds CustomizationEnablement definition.
schema/actions.schema.json Updates SessionCustomizationToggledAction schema to use enablement array and new description.
schema/commands.schema.json Propagates updated customization schemas into commands schema.
schema/notifications.schema.json Propagates updated customization schemas into notifications schema.
schema/errors.schema.json Propagates updated customization schemas into errors schema.
scripts/generate-swift.ts Extends Swift generator for CustomizationEnablement and related enums; adds exhaustiveness coverage.
scripts/generate-rust.ts Extends Rust generator for CustomizationEnablement and related enums; wires into actions/state generation.
scripts/generate-kotlin.ts Extends Kotlin generator for CustomizationEnablement and related enums; adds serializer + exhaustiveness coverage.
scripts/generate-go.ts Extends Go generator for CustomizationEnablement and related enums; adds exhaustiveness coverage.
docs/guide/customizations.md Documents scoped enablement model and updated toggle action semantics.
docs/guide/mcp.md Updates MCP server docs to describe scoped enablement and new client-bundled metadata.
docs/guide/actions.md Updates action guide text for session/customizationToggled semantics.
docs/.changes/20260804-scoped-customization-enablement.json Adds changelog fragment for the enablement model change.
docs/.changes/20260812-client-bundled-customization-enablement.json Adds changelog fragment for client-bundled MCP metadata and child enablement publication.
types/test-cases/round-trips/027-agent-customization-model-and-tools.json Updates round-trip fixture to remove legacy enabled on plugin containers.
types/test-cases/reducers/263-session-customizationtoggled-clears-enablement.json Adds reducer fixture validating clearing enablement restores defaults.
types/test-cases/reducers/239-session-mcpserverstartrequested-no-op-unknown-id.json Updates reducer fixture to remove legacy enabled flags from plugin/MCP server entries.
types/test-cases/reducers/237-session-mcpserverstoprequested-stops-container-child.json Updates reducer fixture to remove legacy enabled flags from plugin/MCP server entries.
types/test-cases/reducers/236-session-mcpserverstoprequested-stops-auth-required-server.json Updates reducer fixture to remove legacy enabled flag from MCP server entries.
types/test-cases/reducers/235-session-mcpserverstartrequested-starts-top-level-server.json Updates reducer fixture to remove legacy enabled flag from MCP server entries.
types/test-cases/reducers/226-session-customizationtoggled-is-no-op-for-unknown-child-id.json Updates reducer fixture to use enablement in toggle action and remove legacy container enabled.
types/test-cases/reducers/225-session-customizationtoggled-toggles-child-by-id.json Updates reducer fixture to use enablement in toggle action and remove legacy flags.
types/test-cases/reducers/162-session-mcpserverstatechanged-noop-non-mcp-id.json Updates reducer fixture to remove legacy container enabled.
types/test-cases/reducers/161-session-mcpserverstatechanged-noop-unknown-id.json Updates reducer fixture to remove legacy MCP server enabled.
types/test-cases/reducers/160-session-mcpserverstatechanged-upserts-container-child.json Updates reducer fixture to remove legacy enabled flags.
types/test-cases/reducers/159-session-mcpserverstatechanged-upserts-top-level-server.json Updates reducer fixture to remove legacy MCP server enabled.
types/test-cases/reducers/154-session-customizationremoved-noop-unknown-id.json Updates reducer fixture to remove legacy plugin enabled.
types/test-cases/reducers/153-session-customizationremoved-removes-child.json Updates reducer fixture to remove legacy container enabled.
types/test-cases/reducers/152-session-customizationremoved-removes-container-and-children.json Updates reducer fixture to remove legacy enabled flags.
types/test-cases/reducers/138-session-customizationupdated-appends-unknown-id.json Updates reducer fixture to remove legacy enabled flags from plugins.
types/test-cases/reducers/137-session-customizationupdated-replaces-existing-container.json Updates reducer fixture to remove legacy enabled flags from plugins.
types/test-cases/reducers/062-session-customizationtoggled-is-no-op-when-customizations-undefined.json Updates fixture to use enablement array in toggle action.
types/test-cases/reducers/061-session-customizationtoggled-is-no-op-for-unknown-id.json Updates fixture to use enablement array in toggle action and remove legacy plugin enabled.
types/test-cases/reducers/060-session-customizationtoggled-toggles-by-id.json Updates fixture to use scoped enablement array and remove legacy plugin enabled.
types/test-cases/reducers/059-session-customizationschanged-replaces-existing-customizations.json Updates fixture to remove legacy plugin enabled.
types/test-cases/reducers/058-session-customizationschanged-replaces-entire-list.json Updates fixture to remove legacy plugin enabled.
clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift Updates Swift session reducer to call updated toggle helper with enablement list.
clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift Implements enablement application logic for Swift client reducer behavior.
clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift Updates Swift generated state types for enablement model and new union.
clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift Updates Swift generated action type for session/customizationToggled.
clients/rust/crates/ahp/src/reducers.rs Implements enablement application logic for Rust client reducer behavior.
clients/rust/crates/ahp-types/src/state.rs Updates Rust generated state types for enablement model and new union.
clients/rust/crates/ahp-types/src/actions.rs Updates Rust generated action type for session/customizationToggled.
clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt Implements enablement application logic for Kotlin client reducer behavior.
clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt Updates Kotlin generated state types for enablement model and new union.
clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt Updates Kotlin generated action type for session/customizationToggled.
clients/go/ahptypes/state.generated.go Updates Go generated state types for enablement model and new union.
clients/go/ahptypes/actions.generated.go Updates Go generated action type for session/customizationToggled.
clients/go/ahp/reducers.go Implements enablement application logic for Go client reducer behavior.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 48/52 changed files
  • Comments generated: 4
  • Review effort level: Lite

Comment on lines 196 to 200
Note over Server: customizations: [Plugin A (enabled), Plugin B (enabled)]

Client->>Server: customizationToggled (id: plugin-a, enabled: false)
Client->>Server: customizationToggled (id: plugin-a, enablement: [session: false])
Server->>Client: action echoed
Note over Server: customizations: [Plugin A (disabled), Plugin B (enabled)]
Comment on lines 465 to 468
Note over Client,Server: 3. Client disables Plugin A

Client->>Server: customizationToggled (id: plugin-a, enabled: false)
Client->>Server: customizationToggled (id: plugin-a, enablement: [session: false])
Server->>Client: action echoed
Comment on lines +1060 to +1064
* The agent host emits at most one Workspace entry, for the session's primary
* working directory. Consumers MAY treat
* `enablement[0]` as the decisive decision and
* `enablement?.[0]?.enabled ?? true` as the effective enabled value. An
* absent or empty array means no explicit decision exists, so the
Comment on lines +848 to +858
/**
* Explicit enablement decisions for children this plugin contributes,
* keyed by child name (for MCP servers, the server name as it appears in
* the bundled `.mcp.json`).
*
* Bundled children are discovered by the host rather than published by the
* client, so the client cannot attach `enablement` to them directly. This
* carries the client's global decision for each one; the host applies it
* under the child's durable key.
*/
childEnablement?: Record<string, CustomizationEnablement[]>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: The council independently confirmed this contract mismatch. The array shape also permits zero or multiple Global entries, even though the field is described as carrying the client's single Global decision for each child. A single Global-only decision value would make both invalid scope and invalid cardinality unrepresentable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Council review identified two additional protocol contract questions.

case CustomizationType.Plugin:
case CustomizationType.McpServer: {
if (enablement.length > 0) {
return { ...customization, enablement: [...enablement] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: session/customizationToggled is client-dispatchable and schema-valid arrays can arrive in any order, but this reducer persists caller order while consumers derive the effective value from element 0. The wire contract says producers MUST sort, yet the server-validation table has no rule for rejecting unsorted or duplicate-scope input, so a malformed client action can be optimistically applied and echoed with the wrong effective state. Please either canonicalize/deduplicate at this boundary in every reducer, or specify server rejection semantics and add shared fixtures for unsorted and duplicate kinds.

Comment thread scripts/generate-go.ts
\t\t}
\t\te.Value = &value
\tdefault:
\t\treturn &json.UnmarshalTypeError{Value: "CustomizationEnablement"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Is CustomizationEnablement intentionally a closed union? Go, Kotlin, and Swift all fail the containing payload on an unknown kind, while AHP clients are released independently and several state unions preserve unknown variants. If another scope may be added within a compatible protocol version, this needs an unknown-preserving branch plus a round-trip fixture; otherwise, please document that adding a scope requires an incompatible protocol version.

@connor4312
Connor Peet (connor4312) merged commit d6bf47d into main Aug 13, 2026
9 checks passed
@connor4312
Connor Peet (connor4312) deleted the session/scoped-customization-enablement branch August 13, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants