Skip to content

Claude agent — Phase 11: customizations / plugins#318113

Draft
TylerLeonhardt wants to merge 3 commits into
mainfrom
tyleonha/claude-phase11-customizations
Draft

Claude agent — Phase 11: customizations / plugins#318113
TylerLeonhardt wants to merge 3 commits into
mainfrom
tyleonha/claude-phase11-customizations

Conversation

@TylerLeonhardt
Copy link
Copy Markdown
Member

Phase 11 of the Claude agent provider in the agent host.

Behaviour

  • Workbench-pushed customizations. setClientCustomizations / setCustomizationEnabled flow through IAgentPluginManager into Options.plugins on the Claude SDK Query.
  • Server-side (SDK-discovered) customizations. Commands / agents / MCP servers from the live Query are projected as a single "Discovered in Claude" Open Plugins-conformant on-disk bundle so the workbench's plugin expander surfaces them next to client-pushed entries.

Design notes

  • `Query.reloadPlugins()` is parameterless — the SDK's plugin URI set is captured into `Options.plugins` at startup and is otherwise immutable. Any client-pushed change therefore triggers a yield-restart through the same rematerializer path used for client-tool changes. `send()`'s pre-flight runs a single rebind when either `toolDiff` or `clientCustomizationsDiff` is dirty; the rematerializer reads `clientCustomizationsDiff.consume()` so the new URI set lands in `Options.plugins` of the rebuilt `Query`.
  • `SessionClientCustomizationsDiff` drives dirty from the model state observable (widened equality covers `nonce`, `displayName`, `description`, `statusMessage`, `agents`, `pluginDir`, status, enablement). Same-URI content refreshes (nonce bumps) now correctly flip dirty.
  • `setClientCustomizations` runs inside the per-session sequencer so a fire-and-forget call from `AgentSideEffects` cannot race a first `sendMessage`.
  • `ClaudeSdkCustomizationBundler` writes a hashed, content-addressed on-disk tree under `IAgentPluginManager.basePath`. Repeated calls with the same SDK snapshot are nonce-stable and skip the rewrite. The on-disk tree is intentionally a cross-session warm cache.

Tests

New `customizations/` test folder mirrors the source structure:

  • `SessionClientCustomizationsDiff` — URI list, nonce, metadata, enablement, dirty + consume semantics.
  • Projector — client + server tier merge with per-URI enablement overlay.
  • Bundler — write layout, nonce stability, name sanitisation, working-directory namespacing, delete-on-change.

`claudeAgent.test.ts`:

  • Sync-and-toggle dispatch through the sequencer.
  • Rebind on customizations dirty (proves `reloadPlugins()` would not have sufficed).
  • Mid-turn race coverage.
  • Swallowed-SDK-snapshot fallback in `getSessionCustomizations`.

Draft for review. No telemetry impact, no proposed-API changes.

Workbench-pushed customizations (setClientCustomizations /
setCustomizationEnabled) flow through IAgentPluginManager into
Options.plugins for the Claude SDK Query. Server-side
(SDK-discovered) commands / agents / MCP servers are projected as a
single "Discovered in Claude" Open Plugins-conformant on-disk bundle.

Notable design notes:

  - The SDK's Query.reloadPlugins() is parameterless and cannot
    change the plugin URI set after startup, so any client-side
    customization change triggers a yield-restart through the same
    rematerializer path used for client-tool changes. send()'s
    pre-flight runs a single rebind when either toolDiff or
    clientCustomizationsDiff is dirty.

  - SessionClientCustomizationsDiff drives dirty from the model
    state observable (not just enabledPluginPaths), so nonce bumps
    and metadata refreshes at the same URI are detected.

  - setClientCustomizations runs inside the per-session sequencer so
    a fire-and-forget call from AgentSideEffects cannot race a first
    sendMessage.

  - ClaudeSdkCustomizationBundler writes a hashed, content-addressed
    on-disk tree under the plugin manager's basePath. Repeated
    calls with the same SDK snapshot are nonce-stable and skip the
    rewrite. The on-disk tree is intentionally a cross-session warm
    cache.

Tests:
  - New customizations/ test folder mirrors the source structure:
    SessionClientCustomizationsDiff (URI list, nonce, metadata,
    enablement, dirty semantics), projector (client+server merge),
    bundler (write layout, nonce stability, name sanitisation,
    namespacing, delete-on-change).
  - claudeAgent.test.ts: sync-and-toggle dispatch, sequencer
    serialisation, rebind on customizations dirty, mid-turn race
    coverage, swallowed-SDK-snapshot fallback in
    getSessionCustomizations.
Copilot AI review requested due to automatic review settings May 23, 2026 16:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 implements Phase 11 of the Claude agent provider in the agent host by wiring client-pushed customizations/plugins into session state and projecting SDK-discovered customizations as an on-disk Open Plugins bundle so the workbench can surface both tiers consistently.

Changes:

  • Add per-session client customization state (synced snapshot + enablement overlay + dirty/consume semantics) and project enabled plugin dirs into Options.plugins at SDK startup/restart.
  • Add SDK-side customizations snapshotting and bundle them into a synthetic “Discovered in Claude” Open Plugins layout under IAgentPluginManager.basePath.
  • Wire the new flows through ClaudeAgent/ClaudeAgentSession and add unit/integration tests plus supporting docs/plan updates.
Show a summary per file
File Description
src/vs/platform/agentHost/test/node/customizations/claudeSessionCustomizationsProjector.test.ts Unit tests for merging client-pushed customizations with the discovered bundle.
src/vs/platform/agentHost/test/node/customizations/claudeSessionClientCustomizationsModel.test.ts Unit tests for client customization diff/model behavior (dirty/consume/enablement).
src/vs/platform/agentHost/test/node/customizations/claudeSdkCustomizationBundler.test.ts Unit tests for bundling SDK-discovered customizations into an Open Plugins tree.
src/vs/platform/agentHost/test/node/claudeSdkPipeline.test.ts Tests for new pipeline reloadPlugins forwarding.
src/vs/platform/agentHost/test/node/claudeSdkOptions.test.ts Tests for projecting plugin directories into SDK Options.plugins.
src/vs/platform/agentHost/test/node/claudeAgent.test.ts Phase 11 coverage: sync dispatch, fan-out toggles, rebind behavior, race coverage, snapshot failure fallback.
src/vs/platform/agentHost/test/node/claudeAgent.integrationTest.ts Stubs IAgentPluginManager for proxy-backed integration coverage.
src/vs/platform/agentHost/node/claude/roadmap.md Roadmap update (adds Phase 16 entry and sequencing note).
src/vs/platform/agentHost/node/claude/phase11-plan.md New Phase 11 plan/design document captured alongside implementation.
src/vs/platform/agentHost/node/claude/customizations/claudeSessionCustomizationsProjector.ts Projects client-pushed customizations + discovered bundle into protocol SessionCustomization[].
src/vs/platform/agentHost/node/claude/customizations/claudeSessionClientCustomizationsModel.ts New per-session observable model + diff tracking for client-pushed customizations/plugins.
src/vs/platform/agentHost/node/claude/customizations/claudeSdkCustomizationBundler.ts New bundler that materializes SDK discovery into a synthetic on-disk Open Plugins bundle.
src/vs/platform/agentHost/node/claude/claudeSdkPipeline.ts Adds plugin reload + resolved-customizations snapshot APIs used by session customization projection.
src/vs/platform/agentHost/node/claude/claudeSdkOptions.ts Adds plugins?: URI[] input and maps it to SDK Options.plugins.
src/vs/platform/agentHost/node/claude/claudeAgentSession.ts Owns per-session client customization state, passes plugins at materialize/rebind, and exposes merged session customizations.
src/vs/platform/agentHost/node/claude/claudeAgent.ts Implements customization-related IAgent surface: syncing, toggling, aggregation event, and session projection.

Copilot's findings

  • Files reviewed: 16/16 changed files
  • Comments generated: 2

return {
customization: {
customization: {
uri: URI.parse(uri),
Comment on lines +89 to +93
if (cur.enablement.get(uri) === enabled) {
return;
}
const next = new Map(cur.enablement);
next.set(uri, enabled);
@TylerLeonhardt
Copy link
Copy Markdown
Member Author

@copilot fix the lint errors and comments

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 23, 2026

@copilot fix the lint errors and comments

Addressed in aa39ee9. I fixed the CI type/lint failures and review feedback by correcting the customization URI test shape, adding the missing SlashCommand argumentHint in tests, and updating setEnabled to avoid dirtying state for default-enabled entries (with regression coverage).

@TylerLeonhardt
Copy link
Copy Markdown
Member Author

@copilot fix the test failure

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 23, 2026

@copilot fix the test failure

Fixed in 4cbfa10. The failing test was due to URI key normalization mismatch (https://a/ vs https://a) in claudeSessionClientCustomizationsModel.test.ts; I aligned the key to the protocol URI string shape so the disable assertion passes.

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.

3 participants