Thanks to visit codestin.com
Credit goes to github.com

Skip to content

agentHost: configure plugins for remote agent hosts#312225

Draft
joshspicer wants to merge 15 commits intomainfrom
agents/configure-remote-agent-plugins
Draft

agentHost: configure plugins for remote agent hosts#312225
joshspicer wants to merge 15 commits intomainfrom
agents/configure-remote-agent-plugins

Conversation

@joshspicer
Copy link
Copy Markdown
Member

Summary

  • persist host-owned plugin customizations in AHP root config so a remote agent host can own plugin configuration
  • plumb root config changes through agent host dispatch/side-effect paths and republish live session customization state
  • distinguish host-configured versus client-synced customizations, including host-wide vs workspace-scoped matching against the original selected workspace
  • expose add/remove/manage flows for remote host plugins in the Sessions AI customization UI
  • sync the paired protocol updates and add focused agentHost/sessions tests

Why

When a user connects from a browser or phone, they may not have any local customization state to sync into the remote host. This change lets the remote AHP host configure plugins directly, then feeds those resolved plugins into the Copilot-backed runtime so sessions can use them without relying on local client state.

Protocol / Spec

Paired draft PR: microsoft/agent-host-protocol#77

Notes for reviewers

  • host-owned plugin refs are stored in agent-host-config.json via AHP root config
  • live sessions now republish session customizations when host customization resolution changes
  • workspace-scoped host plugins match against the session's original customization directory rather than an isolated worktree path
  • this is still configuration of existing remote plugin folders, not a full remote install/download flow

Validation

  • npm run compile-check-ts-native
  • node ./build/hygiene.ts $(git diff --name-only)
  • npm run valid-layers-check
  • npm run transpile-client
  • npm run test-node -- --run src/vs/platform/agentHost/test/node/agentService.test.js src/vs/platform/agentHost/test/node/agentSideEffects.test.js src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostCustomizationHarness.test.js

./scripts/test.sh still does not boot Electron correctly in this shell (app is undefined during startup), so the focused node runner remains the reliable validation path here.

Copilot AI review requested due to automatic review settings April 23, 2026 21:17
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 enables remote Agent Host Protocol (AHP) servers to own and persist plugin customization configuration (rather than relying on client-side sync), and wires those host-owned plugins through the agent host state/actions pipeline into the Sessions AI customization UI.

Changes:

  • Persist host-owned plugin customizations in agent host root config (agent-host-config.json) and plumb root/configChanged through server dispatch + side effects to republish live customization state.
  • Extend customization harness descriptors and the Plugins UI to support remote plugin items plus harness-provided toolbar/actions (add/remove/manage flows for remote hosts).
  • Update protocol/state typings and add targeted agentHost + sessions tests for root config and customization republishing behavior.
Show a summary per file
File Description
src/vs/workbench/contrib/chat/common/customizationHarnessService.ts Adds pluginActions, per-item actions, and itemKey to support remote/plugin management UI.
src/vs/workbench/contrib/chat/browser/aiCustomization/pluginListWidget.ts Renders remote plugin entries, groups them, and allows harness-driven toolbar + item context actions.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/loggingAgentConnection.ts Updates dispatch typing to StateAction for broader action plumbing.
src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostCustomizationHarness.test.ts New browser tests for remote host plugin item identity and removal behavior.
src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostCustomizationHarness.ts Implements remote host plugin controller + provider; maps root/session state to plugin list items & actions.
src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts Wires remote harness registration with plugin controller, provider, and required services.
src/vs/sessions/AI_CUSTOMIZATIONS.md Documents external harnesses + pluginActions and how plugins are surfaced for remote harnesses.
src/vs/platform/agentHost/test/node/mockAgent.ts Extends mock agent to support host/session customization APIs used by new side effects + tests.
src/vs/platform/agentHost/test/node/agentSideEffects.test.ts Adds coverage for republishing customizations on root config changes + clearing client customizations.
src/vs/platform/agentHost/test/node/agentService.test.ts Adds coverage ensuring root config changes are persisted to agent-host-config.json.
src/vs/platform/agentHost/node/protocolServerHandler.ts Validates client-dispatchable actions (incl root actions) before forwarding to agent service.
src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts Adds customizationDirectory so workspace-scoped host plugins resolve against the intended root.
src/vs/platform/agentHost/node/copilot/copilotAgent.ts Implements host-owned + session-effective customization resolution and plugin parsing flow updates.
src/vs/platform/agentHost/node/agentSideEffects.ts Applies host config to agents and republishes agent/session customizations when root config changes.
src/vs/platform/agentHost/node/agentService.ts Accepts root config resource, persists root config on RootConfigChanged, and initializes providers with host customizations.
src/vs/platform/agentHost/node/agentHostStateManager.ts Broadens client dispatch to StateAction and exposes session URIs for republish flows.
src/vs/platform/agentHost/node/agentHostServerMain.ts Provides default persisted root-config path when starting standalone agent host server.
src/vs/platform/agentHost/node/agentHostMain.ts Provides default persisted root-config path when starting agent host utility process.
src/vs/platform/agentHost/node/agentConfigurationService.ts Introduces root config load/validate/persist support and initializes root config state.
src/vs/platform/agentHost/electron-browser/agentHostService.ts Updates action dispatch typing to StateAction.
src/vs/platform/agentHost/common/state/protocol/version/registry.ts Registers new action version mapping (incl SessionActivityChanged).
src/vs/platform/agentHost/common/state/protocol/state.ts Adds customization scoping + source metadata and session activity field.
src/vs/platform/agentHost/common/state/protocol/reducers.ts Updates reducer and isClientDispatchable to cover root actions; adds session activity reducer case.
src/vs/platform/agentHost/common/state/protocol/actions.ts Adds SessionActivityChanged action and extends StateAction union.
src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts Regenerates action origin unions and dispatchability map for new root/session actions.
src/vs/platform/agentHost/common/state/protocol/.ahp-version Bumps synced protocol version hash.
src/vs/platform/agentHost/common/state/agentSubscription.ts Broadens optimistic dispatch signature to StateAction.
src/vs/platform/agentHost/common/agentService.ts Extends agent interfaces for host/session customizations and updates dispatch typing to StateAction.
src/vs/platform/agentHost/common/agentHostCustomizationConfig.ts New schema + helpers for persisted host customization config and scope matching.
src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts Updates dispatch typing to StateAction.
src/vs/platform/agentHost/browser/nullAgentHostService.ts Updates dispatch typing to StateAction.

Copilot's findings

Comments suppressed due to low confidence (1)

src/vs/platform/agentHost/common/state/agentSubscription.ts:464

  • dispatchOptimistic now accepts StateAction (so clients can send root actions like root/configChanged), but it still only applies optimistic updates for session actions. This means root-config updates won’t be reflected locally until the server echo arrives, and it also conflicts with the comment that root state is “server-only mutations”. Either implement write-ahead/reconciliation for client-dispatchable root actions (at least RootConfigChanged) or explicitly document/handle why root actions are excluded here.
	dispatchOptimistic(action: StateAction): number {
		if (isSessionAction(action)) {
			const entry = this._subscriptions.get(URI.parse(action.session));
			if (entry && entry.sub instanceof SessionStateSubscription) {
				return entry.sub.applyOptimistic(action);
			}
		}
		return this._seqAllocator();
	}
  • Files reviewed: 31/31 changed files
  • Comments generated: 1

Comment on lines +958 to 962
private async filterPlugins(): Promise<void> {
const query = this.searchQuery.toLowerCase().trim();
const allPlugins = this.agentPluginService.plugins.get();
this.remoteItems = [...await this.getRemotePluginItems(query)];

Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

filterPlugins is now async and awaits remote provider results, but there’s no concurrency guard. Multiple calls (search typing, autorun refreshes, toggleGroup, etc.) can overlap and apply results out of order, leading to stale list contents. Consider tracking a request id / CancellationTokenSource per filter run and only applying results for the latest invocation; also ensure callers (e.g. refresh) await/return the promise or explicitly void it to avoid unhandled rejections.

Copilot uses AI. Check for mistakes.
@joshspicer joshspicer force-pushed the agents/configure-remote-agent-plugins branch 2 times, most recently from 655622b to 5266a5c Compare April 23, 2026 23:14
joshspicer and others added 8 commits April 23, 2026 17:12
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Delete AgentCustomizationSyncProvider and its tests. The opt-out
ICustomizationDisableProvider is now the only sync model.

Co-authored-by: Copilot <[email protected]>
- 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 <[email protected]>
@joshspicer joshspicer force-pushed the agents/configure-remote-agent-plugins branch from 5266a5c to b55a44d Compare April 24, 2026 00:13
joshspicer and others added 7 commits April 23, 2026 17:18
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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.

Copilot's findings

Comments suppressed due to low confidence (1)

src/vs/workbench/contrib/chat/browser/aiCustomization/pluginListWidget.ts:963

  • filterPlugins is now async (awaiting remote provider results). Please ensure all call sites treat it as such (use await/return/void consistently). At least refresh() currently calls it without awaiting, which can leave a floating promise and make refresh complete before the list state is updated.
  • Files reviewed: 49/49 changed files
  • Comments generated: 2

Comment on lines +457 to +458
* `SKILL.md`, but the local-sync bundler writes them as flat files;
* both layouts are accepted.
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The comment says “local-sync bundler writes [skills] as flat files”, but SyncedCustomizationBundler now preserves per-skill subdirectories for SKILL.md. Updating this comment to reflect the current on-disk layout (and/or clarifying that legacy bundles or non-SKILL.md skill files may still be flat) would prevent confusion when debugging expansion behavior.

Suggested change
* `SKILL.md`, but the local-sync bundler writes them as flat files;
* both layouts are accepted.
* `SKILL.md`, and synced bundles may preserve that per-skill
* directory layout. Flat skill files can still appear for legacy
* bundles or non-`SKILL.md` skill files, so both layouts are
* accepted.

Copilot uses AI. Check for mistakes.
Comment on lines +660 to 665
if (itemProvider) {
itemProviderChangeDisposable.value = itemProvider.onDidChange(() => {
if (!this.browseMode) {
this.refresh();
}
});
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Inside the itemProvider onDidChange handler, this.refresh() is called without await/void, which leaves a floating promise (and can drop errors if refresh ever starts throwing). Use void this.refresh() (or make the callback async and await).

This issue also appears on line 959 of the same file.

Copilot uses AI. Check for mistakes.
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.

2 participants