* agentHost: dedupe concurrent _resumeSession calls per sessionId
Concurrent _resumeSession(id) callers (e.g. an outdated-config refresh in
sendMessage plus a getSessionMessages subscribe) each constructed a fresh
CopilotAgentSession and ran it through _createAgentSession, whose
this._sessions.set(id, …) on a DisposableMap synchronously disposed the
first entry mid-initializeSession(). The result was a tight loop of
'Trying to add a disposable to a DisposableStore that has already been
disposed' warnings (~550 in one repro) and a half-initialised session
with no event subscriptions — the chat widget opens, but sending a
message goes nowhere.
Fix:
- Add an _resumingSessions: Map<string, Promise<CopilotAgentSession>>;
_resumeSession is now a thin wrapper that returns the in-flight promise
when one exists, else delegates to a new _doResumeSession and memoizes
the promise until it settles.
- Stop registering sessions in _sessions inside _createAgentSession.
Both callers (_doResumeSession and _materializeProvisional) now set
_sessions only after initializeSession() succeeds, and dispose the
freshly-constructed agentSession if it throws. This removes the
DisposableMap.set footgun that disposed an in-flight entry from under
its own init.
Adds focused tests for the dedup contract:
- concurrent calls for the same id share one promise + one _doResumeSession
- inflight entry is cleared after resolution (next call re-invokes)
- inflight entry is cleared after rejection (next call retries)
- different ids resolve independently
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: guard post-init _sessions.set against shutdown race
Address Copilot review on #318636: now that _sessions.set is deferred
until after initializeSession() resolves, an in-flight _resumeSession /
_materializeProvisional whose init resolves AFTER dispose() ->
shutdown() -> super.dispose() has run would call _sessions.set(...) on
a disposed DisposableMap, leaking the session and reproducing the
'Trying to add a disposable to a DisposableStore that has already been
disposed' warning this PR exists to eliminate.
Extract a small _registerInitializedSession helper that bails (dispose
+ CancellationError) when _shutdownPromise is already set, and route
both call sites through it. The provisional path additionally still
removes its created worktree on cancel via the existing catch block.
Add a focused unit test that exercises the helper directly with a fake
session, simulating the shutdown-started state.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* launch skill: call out the transpile-client → preLaunch-skips-compile trap
When 'out/' already exists from a prior 'npm run transpile-client',
'node build/lib/preLaunch.ts' will skip 'npm run compile' and you'll
get a launched window whose built-in extensions fail to activate with
'Cannot find module .../extensions/.../out/...'. Document the trap in
both prerequisites and troubleshooting, and add a 'node_modules / npm
install' prereq.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* launch skill: tighten compile prereq + sign-in nudge
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* mcp: prefer announced server title for tool prefixes and picker labels
Reworks how MCP tool prefixes are generated and how MCP servers are labelled
in the Configure Tools picker so that the server-announced serverInfo.title
(falling back to serverInfo.name) is preferred over the mcp.json key.
- Introduces a shared McpPrefixGenerator with a ake(name): IReference<string>
API that hands out collision-resolved tool prefixes and releases them on
dispose. Replaces the eager one-shot generator that used to live in
McpService.
- McpServer now derives its tool prefix from its preferred name (announced
title when known, otherwise the mcp.json label) so registry-style names like
io.github.upstash/context7 no longer end up as truncated
mcp_io_github_ups_* prefixes.
- Configure Tools picker and ToolDataSource.classify for MCP sources now
prefer serverLabel (the announced title) over label .
- Adds unit tests for McpPrefixGenerator covering collisions, slot reuse on
dispose, bucket cleanup, and name sanitization.
Fixes https://github.com/microsoft/vscode/issues/299749
Fixes https://github.com/microsoft/vscode/issues/299787
(Commit message generated by Copilot)
* pr comments
* Claude agent — Phase 11: customizations/plugins
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.
* fix: address customizations lint and review feedback
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
* test: fix customizations enablement key mismatch
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
* Fix Windows path assertions in Phase 11 tests
URI.file('/p/a').fsPath is '\p\a' on Windows, so the literal POSIX
string comparisons fail there. Compute expected via URI.file().fsPath
so the same path round-trip drives both sides of the assertion.
* Phase 11 docs: reflect shipped rebind-always architecture
The original plan described setCustomizationEnabled as defer-and-coalesce
via Query.reloadPlugins() with a tool-set-divergence escalation to rebind.
Council review during PR #318113 verified Query.reloadPlugins() is
parameterless in @anthropic-ai/claude-agent-sdk and cannot change the
plugin URI set captured into Options.plugins at startup, so any client-
pushed customization change ships as a yield-restart through the same
rematerializer path that client-tool changes use.
Rewrites the Phase 11 sections of roadmap.md and phase11-plan.md so the
docs match what was merged. Historical "original plan called for X"
notes preserved for context. Phase 11 marked DONE on the roadmap.
* Claude phase 11: agent picker plumbing + on-disk URIs
- Add IAgent.changeAgent for Claude: pre-materialize stash, post-materialize
rebind via dirty bit (SDK has no working runtime control to swap agent in
place — applyFlagSettings({ agent }) exists but doesn't actually swap).
- Thread Options.agent through buildOptions / materialize / rematerializer
and persist selection in the per-session metadata overlay so resume
picks it up.
- ClaudeSdkCustomizationBundler now publishes CustomizationAgentRef.uri
as the on-disk `agents/<name>.md` path (was a synthetic
`claude-sdk-agent:/` scheme). The workbench customization harness
needs a real file URI to parse via promptsService.parseNew — without
it the agents never reached the picker.
- Hide 'general-purpose' (SDK default) from the picker via shared
CLAUDE_SDK_DEFAULT_AGENT_NAME constant.
- Tests: 3 changeAgent cases (provisional / mid-session rebind /
clear-to-undefined), bundler agent-URI shape.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Decompose parsed plugins into their full set of child customizations (agents, skills, prompts/commands, rules, hooks, MCP servers) from a single point of truth in the parser, instead of synthesizing only agents ad-hoc inside the copilot agent.
- Adds protocol-level projection types to the parsed primitives: each parsed agent/skill/rule now exposes a typed customization field, and IParsedHookGroup/IMcpServerDefinition carry their HookCustomization/McpServerCustomization. parsePlugin() mints these at parse time using customizationId-based ids.
- Introduces toChildCustomizations(plugins) in copilotPluginConverters that walks all parsed primitives and emits a deduped ChildCustomization[]. The copilot agent's host/synced/session-discovered resolvers now populate Customization.children via this single helper instead of calling toAgentCustomizations(plugin.agents) ad-hoc.
- Address Copilot review feedback: customizationId now percent-encodes existing '#' and uses a reserved '#range=' suffix so ranged ids cannot collide with URI fragments.
- mockAgent.setCustomizationEnabled now records the opaque id so tests can catch id !== uri regressions.
- Updates pluginParsers / copilotPluginConverters / convertBareEnvVarsToVsCodeSyntax tests to satisfy the new customization-bearing definition shapes; adds a thin test alias for convertBareEnvVarsToVsCodeSyntax that fills in the irrelevant customization stub.
(Commit message generated by Copilot)
Brings the platform and workbench code in line with the reorganized AHP customization types: containers (PluginCustomization, DirectoryCustomization) carry the children (agents, skills, prompts, rules, hooks, MCP servers) discovered inside them.
- Replaces CustomizationRef/SessionCustomization/CustomizationAgentRef with the new Customization tree, plus ClientPluginCustomization for client-published plugins. Adds a customizationId(uri, range?) helper for minting session-unique ids that disambiguate inline manifest declarations.
- Updates IAgent and IAgentPluginManager: getCustomizations/getSessionCustomizations return Customization[]; setClientCustomizations/syncCustomizations take ClientPluginCustomization[]; setCustomizationEnabled now takes the opaque id.
- Reworks copilotAgent's PluginController + SessionDiscoveredEntry around the flat Customization shape with the CustomizationLoadStatus enum and a children array; SessionCustomizationUpdated dispatches the whole entry.
- Restores the legacy plugin-only agentHostCustomizationConfigSchema so existing agent-host-config.json files keep working; entries are lifted into the new Customization container shape at read time by getAgentHostConfiguredCustomizations / toContainerCustomization.
- Renames toCustomizationAgentRefs to toAgentCustomizations; drops the redundant enabled flag on ChildCustomization (children inherit their container's enablement). customAgents.getEffectiveAgents now walks children filtered by CustomizationType.Agent.
- Updates the workbench item provider, active-client service, harnesses, custom-agent pickers, and the remote harness to the new types; toStatusString/toStatusMessage drive UI status off CustomizationLoadStatus.
- Adapts all consumer tests (reducers, agentPluginManager, agentSideEffects, copilotAgent, mockAgent, syncedCustomizationBundler, resolveCustomizationRefs, localAgentHostSessionsProvider, remoteAgentHostCustomizationHarness, agentHostAgents/AgentPicker) to the new shapes.
- Cleans up a stale URI auto-generated import in channels-session/actions.ts.
(Commit message generated by Copilot)
Remove leftover code from /tests test-generation codelens
The codelens feature for /tests test generation was removed in
c0eaa94d6a, but related infrastructure remained as dead code:
- ITestGenInfoStorage was never populated (the deleted testGenAction.ts
was the only writer), so TestFromTestInvocation and Test2Impl always
took the 'undefined' branch.
- IParserService.getTreeSitterAST().getTestableNode/getTestableNodes
and the underlying testGenParsing helpers + testableNodeQueries had
no remaining callers.
Delete the storage service, the parser API, the tree-sitter queries,
and the related tests. The /tests slash command itself is preserved;
`_findLastTest` stays since testFromSrcInvocation.tsx still uses it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements a 3-legged OAuth flow for enterprise-managed MCP servers where
VS Code routes per-resource authentication through a tenant-wide IdP via
ID-JAG (draft-ietf-oauth-identity-assertion-authz-grant) token exchange:
1. User signs in once to the enterprise IdP (Auth Code + PKCE via the
existing DynamicAuthProvider base class). The IdP id_token is stored
in OS secret storage and survives window reload.
2. The id_token is exchanged at the IdP for a resource-scoped ID-JAG
assertion (RFC 8693 token exchange, subject_token_type=id_token,
requested_token_type=id-jag, audience=<resource AS>).
3. The ID-JAG is redeemed at the resource's authorization server
(grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer) for a
resource-scoped access token.
4. The resource access token is used to call the MCP server.
Surfaces:
- New 'mcp.enterpriseManagedAuth.idp' setting (issuer/clientId/clientSecret),
delivered via policy (McpEnterpriseManagedAuthIdp). The setting is
hidden from Settings UI (included: false) but readable/writable by hand
for local dev. APPLICATION scope so it never syncs.
- New 'enterpriseManaged' flag on MCP HTTP server entries triggers the
XAA flow instead of per-server Dynamic Client Registration.
- New proposed API 'authSessionAudience' adds optional 'audience' to
AuthenticationProviderSessionOptions so the XAA provider can receive
the resource AS URL through the standard session options shape.
- IAuthenticationService grows createOrGetXaaProvider(issuer): registers
one XAA provider per IdP issuer (shared across enterprise MCP servers).
- Resource-AS client secrets (distinct from IdP client secrets) are stored
in OS secret storage keyed by (resource indicator, resource client_id)
and resolved through the existing 'Set Client Secret' codelens above
oauth.clientId in mcp.json, with a prompt fallback for first run.
Silent re-mint on reload: getSessions reads the persisted IdP session
from base-class secret storage and silently runs legs 2-4 to produce a
resource token without prompting. Only escalates to createSession when
the resource AS needs interactive client-secret entry.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert the agent-host short-picker set to a map so each picker can have
its own min-width. The tunnel-sharing toggle has no chevron, so it
collapses to 16px instead of 22px.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>