Skip to content

agentHost: surface client custom agents when opening an existing session#324715

Draft
sandy081 wants to merge 1 commit into
mainfrom
agents/custom-agents-not-visible-in-session
Draft

agentHost: surface client custom agents when opening an existing session#324715
sandy081 wants to merge 1 commit into
mainfrom
agents/custom-agents-not-visible-in-session

Conversation

@sandy081

@sandy081 sandy081 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Problem

In the Agents Window, opening an existing agent-host session (e.g. Copilot CLI) shows no custom agents in the agent picker — only "Agent" — even though the user has installed plugins/custom agents in VS Code. Creating a new session shows them correctly.

Root cause / AH design

The agent picker's list comes from getEffectiveAgents(SessionState.customizations) — the host's merged view of three customization sources:

  1. Host-configured — the agent's own plugin dirs (also in AgentInfo.customizations at root).
  2. Session-discovered — dirs the host watches in the working directory.
  3. Client-contributed — the plugins the user installed in VS Code.

Client-contributed customizations are push-only, client → host, via session/activeClientSet. There is no host pull API for them: the host cannot enumerate the VS Code client's installed plugins; it only learns the specific refs the client publishes and then reads/expands those files.

The client publishes in exactly two places today:

  • createSession(...) for a new session (carries activeClient), and
  • the first turn (_ensureActiveClientForMessage).

provideChatSessionContent deliberately does not register the active client on open (see comment added in e719715c9f6 / "support multiple active clients per session"), to avoid changing tool-dedup ownership for another client's in-flight turn. The host also drops active clients on disconnect. Result: a reopened existing session (or one created before the plugins were installed) has no client customizations in its SessionState, so the picker is empty.

Fix

Decouple "here are my customizations" from "I'm the client driving this turn (my tools win dedup)":

  • On opening an existing session, publish a customizations-only active-client entry (tools: []) via _publishClientCustomizationsOnOpen. No-op if this client is already an active client, so a real turn's tool contribution is never downgraded.
  • Extend the customization-sync autorun to also cover the resolve-after-open ordering: publish customizations-only for an opened existing (!_isNewSessionResource) session that isn't yet an active client when the client's customizations resolve after open.
  • Tools stay deferred to the first turn (_ensureActiveClientForMessage), so merely viewing a session never changes tool ownership for another client's in-flight turn — honoring the original multi-active-client design intent.

The customization-sync autorun now preserves the existing entry's tools ({ ...existing, customizations }) instead of pulling fresh tools, mirroring the sibling tools autorun ({ ...existing, tools }).

Why not read local prompt/agent files in the picker?

That's what the regular editor does for its local sessions, but for agent-host sessions it would bypass the host's authoritative enabled/expanded/dedup view (children parsing, disabled state, session-type filtering). Keeping the host as the single source of truth is the robust design.

For discussion (@connor4312)

This intentionally relaxes the "don't register the active client on open" rule — but only for customizations, never tools. Does publishing a tools: [] active-client entry on open raise any concerns on the host side (e.g. requiresRestart on a live session, or active-client churn while another client is mid-turn)? The restart check is on sendMessage from an active client, so an on-open customizations-only publish should not restart another client's in-flight turn — but I'd like your read before we land this.

Testing

  • agentHostClientTools.test.ts: added a test asserting that opening an existing session dispatches a single SessionActiveClientSet with tools: [] and the client's customizations.
  • npm run typecheck-client is clean. (The full electron unit-test harness needs a local build; CI runs it.)

The Agents Window agent picker lists custom agents from
`getEffectiveAgents(SessionState.customizations)` — the host's merged view of
host-configured, session-discovered, and client-contributed plugins.

Client-installed VS Code plugins reach the host only via
`session/activeClientSet` (push-only; there is no host pull API for them).
`provideChatSessionContent` intentionally does not register the active client
on open (to avoid changing tool-dedup ownership for another client's in-flight
turn), and the host drops active clients on disconnect. So a session created in
this process shows the client's custom agents (createSession carries
`activeClient`), but a reopened existing session — or one created before the
plugins were installed — shows none.

Publish a customizations-only active-client entry (`tools: []`) on open, and
extend the customization-sync autorun to also publish for an opened existing
session when the client's customizations resolve after open. Tools stay
deferred to the first turn (`_ensureActiveClientForMessage`), so viewing a
session never changes tool ownership for another client's in-flight turn.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 09:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes a bug in the Agents Window where opening an existing agent-host session (e.g. Copilot CLI) showed no client-contributed custom agents/plugins in the agent picker, while creating a new session showed them correctly. The agent picker derives its list from getEffectiveAgents(SessionState.customizations), and client-installed plugins only reach the host via a push-only session/activeClientSet message. Because provideChatSessionContent deliberately avoided registering the active client on open (to preserve tool-dedup ownership for another client's in-flight turn), a reopened session had no client customizations. The fix decouples "publishing customizations" from "claiming tool ownership": it publishes a customizations-only (tools: []) active-client entry on open, while still deferring tools to the first turn.

Changes:

  • Add _publishClientCustomizationsOnOpen (replacing _dispatchActiveClient) and call it from provideChatSessionContent for existing sessions; no-op if this client is already active so tool contributions are never downgraded.
  • Extend the customization-sync autorun to preserve an existing entry's tools ({ ...existing, customizations }) and to publish customizations-only for an opened existing session whose customizations resolve after open.
  • Add a unit test and document the design decision in the sessions skill file.
Show a summary per file
File Description
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts Introduces customizations-only publish on open, reworks the customization-sync autorun to preserve existing tools and cover resolve-after-open, and removes the tools-pulling _dispatchActiveClient.
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostClientTools.test.ts Adds a test asserting provideChatSessionContent on an existing session dispatches a SessionActiveClientSet with tools: [] and the client's customizations; adds a mock active-client service.
.github/skills/sessions/SKILL.md Documents the "publish customizations on open, defer tools to first turn" rule.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Medium

Comment on lines +753 to +758
// client yet. Publish customizations-only now (tools stay
// deferred to the first turn) so the picker still lists them.
// New sessions are skipped: they publish via `createSession`.
this._dispatchAction(backendSession, {
type: ActionType.SessionActiveClientSet,
activeClient: { clientId, tools: [], customizations: [...refs] },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tools should be readily accessible here via this._activeClientService.getClientTools(this._config.sessionType)

this._dispatchAction(backendSession, {
type: ActionType.SessionActiveClientSet,
activeClient: { ...current, customizations },
activeClient: { clientId, tools: [], customizations: [...customizations] },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Likewise here.

I feel like we can probably dedupe this method, and the two autoruns where we call getClientTools and getCustomizations

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