SIGN IN SIGN UP

agentHost: configure plugins for remote agent hosts (#312225)

* agentHost: sync protocol types and add host config schema

Sync CustomizationRef.scope, SessionCustomization.source, and
RootConfigChanged action from the agent-host-protocol spec branch
(8d19730). Add agentHostCustomizationConfig.ts with the JSON schema
and validation helpers for host-owned plugin configuration.

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

* agentHost: add server-side plugin pipeline and config persistence

Add AgentConfigurationService for root config read/write/persist to
agent-host-config.json. Wire root action dispatch and side effects so
that host plugin changes are persisted and republished as session
customizations. Implement PluginController in copilotAgent.ts to merge
host-owned and client-synced plugins, resolve them into IParsedPlugin
snapshots, and feed them into the SDK via _buildSessionConfig().

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

* agentHost: add remote plugin harness and plugin list UI

Add RemoteAgentPluginController for add/remove plugin commands and
RemoteAgentCustomizationItemProvider that surfaces host-owned and
session-synced plugins. Expand each plugin directory via IFileService
to discover individual skills, agents, instructions, and prompts for
per-type sections. Extend pluginListWidget with remote item rendering,
toolbar actions, group headers, and live refresh on itemProvider
changes.

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

* agentHost: add tests for remote plugin configuration

Add tests for host config dispatch, side effect republishing, scope
filtering, and the remote harness item provider (distinct item keys
for scope-distinct plugins, client-synced vs host-owned separation).

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

* agentHost: add disable provider and local customization helpers

Add ICustomizationDisableProvider interface (opt-out model) alongside the
existing ICustomizationSyncProvider. Add AgentCustomizationDisableProvider
implementation and LocalAgentHostCustomizationItemProvider with shared
enumerateLocalCustomizationsForHarness() and resolveCustomizationRefs()
helpers.

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

* agentHost: switch sync consumers to opt-out disable model

Replace ICustomizationSyncProvider usage with ICustomizationDisableProvider
across all consumers. Simplify ProviderCustomizationItemSource to a single
required itemProvider (no more dual local/remote path). Update harness
descriptors to use disableProvider. Listen to both disable provider and
prompts service change events for auto-sync. Add throttling for bundler
re-resolves.

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

* agentHost: remove legacy opt-in sync provider

Delete AgentCustomizationSyncProvider and its tests. The opt-out
ICustomizationDisableProvider is now the only sync model.

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

* agentHost: polish remote customization UI and fix client-sync expansion

- Fix synthetic bundle URI scheme: synced-customization:// URIs must not
  be wrapped as agent-host:// since the server lacks that scheme; expansion
  now reads the client in-memory FS directly
- Suppress client-synced plugin entries from the Plugins list (they are
  already shown under 'Enabled Locally'); their contents still expand into
  per-type tabs (Skills, Agents, etc.)
- Propagate childGroupKey through expansion so host-originated items land
  in the 'Remote' group and client-synced items land in 'Client' group
- Rename groups to 'Remote' / 'Client' in both the Plugins view and the
  per-type tabs
- Remove redundant 'Remote Host' badge from host-configured plugin items
- Add dotfile filter (.DS_Store etc.) in _collectFromTypeDir
- Include PromptsStorage.extension in SYNCABLE_STORAGE_SOURCES so built-in
  extension skills reach the agent host SDK
- Remove checkbox column from Agent Customizations list widget
- Enhance debug report with installed harnesses and plugins (Stages 5-6)
- Fix test: add IPromptsService stub to agentHostChatContribution test

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

* agentHost: fix skill name collision in synced customization bundler

Skills are conventionally directories containing SKILL.md, so the file
locator returns URIs like `/skills/skill-a/SKILL.md`. The bundler used
`basename(uri)` for the destination filename, causing every skill to
overwrite the same `skills/SKILL. only the last one survived.md`

Fix: detect SKILL.md filenames and preserve the parent directory
structure (`skills/{skillName}/SKILL.md`), matching the Open Plugin
convention that the expansion code already handles.

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

* agentHost: tighten dispatchAction type to ClientAction

Reverts the broad StateAction widening on the dispatchAction/dispatch
interface boundary. Instead of accepting any StateAction (which includes
server-only emissions like SessionReady, SessionDelta, etc.), the API
now accepts the precise ClientAction union:

  ClientAction = ClientRootAction | ClientSessionAction | ClientTerminalAction

This restores compile-time safety: passing a server-only action to
dispatch() is now a type error. The new type is exported from
sessionActions.ts alongside the existing SessionAction/TerminalAction
aliases.

Internal server-side state management (reducers, subscriptions,
dispatchServerAction, dispatchClientAction) continues to use the
broad StateAction type as appropriate.

The one root action clients legitimately dispatch (RootConfigChanged,
used to push plugin configuration changes) is included in
ClientRootAction, so the remote harness's dispatchCustomizations()
call compiles correctly.

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

* agentHost: show client-synced plugins as distinct entries in provider

The provideChatSessionCustomizations method was skipping client-synced
items from the top-level items map with the comment that the local
'Enabled Locally' section already shows them. This is wrong for the
remote harness  there is no local section, and client-syncedcontext
plugins should appear as distinct 'Client' group entries alongside
host-owned 'Remote' group entries.

Fixes the failing test:
  RemoteAgentHostCustomizationHarness
    provider keeps client-synced entries distinct from host-owned entries

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

* agentHost: add tests for recent remote plugin fixes

Covers:
- Client-synced vs host-owned group assignment (remote-client/remote-host)
- Synthetic bundle hidden from top-level but expanded for children
- Synced-customization scheme URI preserved (not wrapped as agent-host://)
- Status/statusMessage propagation from session customizations
- Change event firing on SessionCustomizationsChanged action
- Remove action only on host items, not client-synced items
- Multi-entry remove dispatch
- Multiple client-synced entries with distinct keys
- SKILL.md collision prevention with subdirectory layout
- Mixed SKILL.md and non-SKILL.md skill coexistence
- Nonce includes subdirectory path for SKILL.md files
- Rebundle clears previous tree
- Bundle description includes file count

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

* agentHost: ensure built-in group always renders last

Dynamic groups (e.g. remote-host, remote-client) were appended after
the built-in group, causing built-in to appear above them. Insert
dynamic groups before the built-in entry so it always stays last.

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

* agentHost: remove workspace-scoped plugin feature

Remove the 'Add Workspace Plugin' button and its backing methods
(addPluginForWorkspace, getWorkspaceScope). Plugins are now always
added at the host scope. Simplifies addConfiguredPlugin accordingly.

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

* agentHost: remove stretched 'Synced' badge from plugin list

The 'Client' group header already communicates that entries are synced
from the client. The full-width 'Synced' badge was redundant and
visually inconsistent with inline badges elsewhere.

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

* agentHost: replace ClientAction alias with explicit union type

Use SessionAction | TerminalAction | RootAction inline everywhere
instead of introducing a ClientAction alias type. The alias was an
unnecessary abstraction since the underlying union is already
descriptive and matches the pattern used throughout the codebase.

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

* agentHost: narrow dispatch type to RootConfigChangedAction

Only RootConfigChangedAction can be dispatched by clients, not the
entire RootAction union. Use the specific action type in dispatch
signatures to make the contract explicit and prevent clients from
accidentally sending server-only root actions.

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

* agentHost: restore vscode-dev-workbench SKILL.md

This file deletion was unrelated to the remote plugin feature.

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

* agentHost: narrow StateAction to specific union in dispatch methods

dispatchOptimistic and dispatchClientAction accepted the broad
StateAction union, but clients can only send SessionAction,
TerminalAction, or RootConfigChangedAction. Narrow the signatures
to match the established dispatch contract.

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

* agentHost: hide client-synced plugins from plugin page

Client-synced items are already shown in the 'Enabled Locally'
section. Showing them again in a separate 'Client' group is
redundant. Skip remote-client items when building plugin page
groups.

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

* tidy

* Refactor agent host customization helpers

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

* Rename 'Client' group header to 'Local' in customization list widget

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

* Fix root config clobber: merge schemas instead of replacing

AgentConfigurationService constructor was overwriting the root config
that AgentHostStateManager had already seeded with platformRootSchema
(permissions). Now merges both schema properties and values so
platform-owned keys like permissions are preserved alongside our
customizations key.

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

* Remove premature customization publish before setClientCustomizations

The _publishSessionCustomizationsSoon call before setClientCustomizations
would publish stale/previous client customization state. The progress
callback and .then() already handle publishing after the agent updates
its internal state, which includes the merged host+client view.

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

* Rename DisableProvider back to SyncProvider

The class/interface/file were unnecessarily renamed from SyncProvider
to DisableProvider in our branch. SyncProvider is a better name since
the class controls which customizations get synced, with disable being
just the opt-out mechanism.

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

* Fix agentService test: assert on customizations key, not full bag

After the root config merge fix, the persisted config now contains
both permissions and customizations keys. The test should assert that
customizations was persisted correctly rather than expecting the full
bag to contain only customizations.

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

* Address Copilot review: fix floating promise, update stale comment

- Add void to unhandled this.refresh() in pluginListWidget itemProvider
  onChange handler (consistent with all other call sites)
- Update comment in remoteAgentHostCustomizationHarness to reflect that
  synced bundles now preserve per-skill subdirectories

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

* Skip host customization apply when unchanged

Add equality check in _applyHostCustomizationsToAgents to avoid
firing setHostCustomizations and downstream publishes when root
config changes for non-customization keys (e.g. permissions).

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

* Refactor: PluginController self-manages host customizations

Move host customization management into PluginController so
the IAgent interface no longer needs setHostCustomizations.
PluginController now:
- injects IAgentConfigurationService
- subscribes to onDidRootConfigChange (new event)
- reads + resolves customizations internally
- fires onDidChange so AgentSideEffects can republish

This removes the external poke from AgentSideEffects and the
seed call from AgentService.registerProvider, addressing
Connor's architectural feedback.

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

* Add tests for onDidCustomizationsChange event wiring

Verify that firing onDidCustomizationsChange on an agent
triggers republishing of agent info and session customizations,
and that disposing the progress listener stops republishing.

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

* Remove SessionCustomizationSource and CustomizationScopeKind

Address Connor's spec PR feedback:

- Remove SessionCustomizationSource enum; use clientId field on
  SessionCustomization to distinguish host vs client origin
- Remove CustomizationScopeKind, CustomizationScope, and scope
  field from CustomizationRef; all host plugins apply to all
  sessions (workspace scoping was plumbed but never user-facing)
- Remove customizationMatchesDirectory() (dead code after scope
  removal)
- Stamp clientId onto client-sourced customizations in
  PluginController.sync()

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

* Wire IAgentConfigurationService into copilotAgent.test setup

PluginController now reads root config from IAgentConfigurationService
in its constructor. The integration test setup didn't register the
service, causing all CopilotAgent tests to fail with 'Cannot read
properties of undefined (reading getRootValue)'.

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

* Wire IAgentConfigurationService into production agent host DI

PluginController now requires IAgentConfigurationService in its
constructor. agentHostServerMain.ts and agentHostMain.ts both
construct CopilotAgent via DI but didn't register the service,
causing the integration test 'Agent Host Server starts with
production agent services registered' to fail because the server
exited prematurely on startup.

Expose configurationService getter on AgentService and register
it in the downstream DI containers.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
J
Josh Spicer committed
46fcc5c18d1a630599e1569a6eb8d347a632f2e4
Parent: 22fd389
Committed by GitHub <noreply@github.com> on 4/27/2026, 5:16:52 PM