Skip to content

[pull] main from microsoft:main#1185

Merged
pull[bot] merged 8 commits intocode:mainfrom
microsoft:main
Apr 26, 2026
Merged

[pull] main from microsoft:main#1185
pull[bot] merged 8 commits intocode:mainfrom
microsoft:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 26, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

roblourens and others added 8 commits April 25, 2026 16:50
)

* Fix: preserve reasoning order when restoring agent host sessions

When re-opening an agent host session, thinking text was rendered up
front instead of being properly interspersed with tool calls. The SDK's
`getMessages()` history bundles reasoning into each `assistant.message`
as `reasoningText` rather than emitting it as a separate event.
`_buildTurnsFromMessages` was reading `content` and tool requests but
ignoring `reasoningText` entirely, so reasoning was either dropped or
came from a different code path that did not honor stream order.

Fix: when handling an `assistant.message`, push a Reasoning response
part (from `reasoningText`) immediately before the Markdown part. This
matches the EH CLI's history-replay shape and keeps reasoning, content,
and tool calls interleaved in their original order.

Also applied to subagent inner messages.

(Written by Copilot)

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

* Update ScriptedMockAgent to use SessionHistoryEvent

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fall back to response.type when response.reason is undefined so
detailedOutcome is always populated for non-success outcomes.
* Cache listSessions result in AgentHostSessionListController

The workbench-side session list controller previously called
listSessions() on every refresh, even though it already maintains an
in-memory cache that's kept in sync via notify/sessionAdded,
notify/sessionRemoved, and notify/sessionSummaryChanged. refresh() is
fired on workspace folder change, trust change, availability change,
and provider change, so it ran far more often than the underlying list
actually changed.

Add a _cacheValid flag, set on first successful listSessions(). After
that, refresh() skips the RPC and just re-emits the cached items.
Failures leave the cache invalid so the next refresh retries.

Cache lifetime is tied to the controller, which is created per agent
registration and disposed on connection replacement, so reconnect
implicitly drops the cache. This matches the AHP rule that
notifications are not replayed on reconnect, and brings the workbench
controller in line with the analogous one-shot caching in
BaseAgentHostSessionsProvider on the sessions-app side.

(Written by Copilot)

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

* Invalidate session list cache on agent host restart

After a listSessions() success the cache is valid and refresh() skips
the RPC. But since AHP notifications are not replayed on reconnect,
the in-memory list would be stale after an agent host restart if the
cache were never reset.

Add resetCache() on AgentHostSessionListController, and wire it to
onAgentHostStart in AgentHostContribution so the next refresh()
following a restart re-fetches via listSessions().

Also make the MockAgentHostService.onAgentHostStart fireable in tests
and add a test covering the reset-on-restart path.

(Written by Copilot)

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Render copilot skill invocations as a tool call

The Copilot SDK now exposes a skill tool that the model uses to load
SKILL.md files. Until now this surfaced in the agent host as the default
tool UI, which doesn't fit. This change does three things:

- Hide the raw `skill` tool call (added to HIDDEN_TOOL_NAMES).
- Synthesize a `tool_start`/`tool_complete` pair from the SDK's
  `skill.invoked` lifecycle event so we can show 'Reading [name]'
  with a link to the skill file. Wired in both the live event path
  and the history-replay path so reconnect renders identically.
- Filter out the synthetic `user.message` events the SDK sometimes
  injects with skill content (data.source !== 'user').

Client side, the chat adapter now tags markdown links pointing at a
SKILL.md file with `?vscodeLinkType=skill` so they render as the
inline skill pill instead of a plain markdown anchor. Doing this on
the client (rather than at the agent host) keeps the agent host
host-agnostic and also upgrades older sessions and other providers
that emit SKILL.md links.

The link rewriter also now preserves the original link text rather
than always collapsing to `[](newHref)`, so a skill named 'plan'
shows up as 'Reading [plan]' rather than 'Reading [SKILL.md]'.

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

* Address review comments and fix CI snapshots

- Add escapeMarkdownLinkLabel helper in htmlContent.ts: only escapes
  '\' and ']' so link labels rendered without re-parsing markdown
  (e.g. the chat skill pill) don't show literal backslashes for safe
  characters like '-' or '.'.
- Use it in synthesizeSkillToolEvents and stateToProgressAdapter's
  rewriteLinkTokenRaw instead of the over-eager escapeMarkdownSyntaxTokens.
- Hash the path/name fallback in getSkillSyntheticToolCallId so the
  synthesized toolCallId never contains '/' (which breaks
  ChatResponseResource.createUri paths).
- Update copilotToolDisplay/mapSessionEvents test snapshots to match
  the actual emitted strings ('Reading skill [plan](...)' /
  'Read skill [plan](...)') and the new hash-based fallback id.
- Add htmlContent unit tests for escapeMarkdownLinkLabel.

(Written by Copilot)

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es (experimental) (#312568)

* Add cache-stable mode for vscode_renameSymbol and vscode_listCodeUsages

These two tools today emit a description that embeds the live list of
registered language IDs and return undefined when no providers are
registered. As language extensions activate during an agent turn (and
sometimes deactivate), the tools' description bytes change and the tools
themselves can disappear from / reappear in the tool array. Both effects
shift bytes inside the tools array prefix and break the provider-side
prompt cache mid-turn.

Add an experimental setting,
`chat.experimental.symbolTools.cacheStable`, that when enabled:

- Always registers both tools, regardless of whether any provider is
  currently registered.
- Uses a static modelDescription that does not include the language list.
- Skips re-firing onDidUpdateToolData on provider changes (no
  re-registration churn).

Tool behavior at invoke time is unchanged: the tools already return an
error when the file's language has no provider, and the static
description tells the model to expect that.

Default is false so no behavior change without explicit opt-in or
experiment assignment.

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

* Update RenameTool/UsagesTool tests for new IConfigurationService dependency

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

* Add unit tests for symbol tools cache-stable mode

Addresses Copilot PR review: covers RenameTool/UsagesTool getToolData()
behavior when chat.experimental.symbolTools.cacheStable is enabled:
- tool data is returned even with no providers registered
- modelDescription does not include 'Currently supported for' list
- modelDescription is byte-stable across provider registrations

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

---------

Co-authored-by: Kevin Kent <kevinkent@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Restore empty link text for non-skill agent-host links

PR #312557 changed all agent-host link rewriting to preserve the
original markdown label, which made every read-tool link render as a
plain blue markdown link instead of the rich inline anchor / file
widget. Only skill links need a non-empty label (so the skill pill can
show the skill name); restore the empty-label behavior for everything
else.

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

* Address Copilot review: preserve image alt text, add skill/image tests

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agent-host: surface session git state via SessionState._meta

The agent host process now computes per-session git state (branch, GitHub
remote, ahead/behind, uncommitted changes) for sessions that have a working
directory and publishes it through the protocol's per-session `_meta`.
The Agents app reads it from `SessionState._meta` (not `SessionSummary`)
and surfaces it via `ISessionWorkspace`, lighting up existing UI in the
Changes view (e.g. ahead/behind indicators, branch info).

Highlights:
- New `AgentHostGitService` (server-side) computes git state by shelling
  out to git; refreshed on session open and after each turn.
- New `SessionMetaChanged` action propagates `_meta` deltas without a full
  list refresh.
- Client-side `AgentHostSessionAdapter` retains `_project` /
  `_workingDirectory` / `_meta` so the workspace observable can be
  rebuilt when only `_meta` changes.
- `baseBranchProtected` is computed client-side from
  `git.branchProtection` config, so the workspace shape no longer carries
  it as a field on `ISessionRepository`.
- `update()` only overwrites `_meta` when the source actually provides
  one (SessionSummary feeds have no `_meta` field), so the polling
  refresh path no longer clobbers good state pushed via `SessionState`.

(Written by Copilot)

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

* Address Copilot review feedback

- Doc updates: reference SessionState._meta (not SessionSummary._meta)
  in agent service interface and the setMeta delta path.
- changesViewModel.activeSessionHasGitRepositoryObs now derives the
  git-backed signal from surfaced git state on the workspace's first
  repository (uncommittedChanges/incomingChanges/outgoingChanges/
  upstreamBranchName) rather than from mere workspace presence, so we
  don't enable git-specific UI for non-git working directories.

(Written by Copilot)

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

* Render branch name in changes view tree root (Written by Copilot)

Plumb 'branchName' through:
- ISessionRepository (new optional field)
- buildAgentHostSessionWorkspace + agentHostSessionWorkspaceKey (gitFields)
- ChangesViewModel.activeSessionStateObs (fall back to workspace repo)
- ChangesViewPane.getTreeRootInfo: only render parens when branchName known

Also subscribe to activeSessionStateObs in the changes-tree autorun so the
root rebuilds once branchName arrives asynchronously.

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

* Show branch name in changes tree root even for non-worktree sessions (Written by Copilot)

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

* fix: wire AgentHostGitService into AgentService at construction time

Previously AgentService was constructed without a git service (the
optional _gitService parameter was never passed), so _attachGitState
always bailed with 'hasGitService=false' and no branch name was ever
computed. The fix creates AgentHostGitService before AgentService and
passes it as the fifth constructor argument.

Also removes debug logging added during investigation.

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

* harden: make IAgentHostGitService a required AgentService dep

The optional `_gitService?` parameter was the root cause of git
metadata never reaching the client: `agentHostMain.ts` and
`agentHostServerMain.ts` constructed AgentService without passing it,
and `_attachGitState` silently bailed at runtime. Making the dep
required forces all callers (now and in the future) to wire it
correctly at compile time.

Also adds a regression test for the `subscribe()` lazy-fire path,
which was the intended client-visible mechanism for surfacing branch
name on sessions that already exist in the state manager.

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

* Revert: only show branch name for worktree sessions

Branch name on non-worktree sessions wasn't a goal. Restores the
original guard so `(branch)` only appears when the session has a
worktree.

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

* cleanups

* Address Copilot review feedback (Written by Copilot)

- changesViewModel: fall back to workspaceRepository.baseBranchName so
  agent-host sessions get branch protection UI when only the workspace
  repo carries baseBranchName
- changesView: add comment explaining the intentional dependency read on
  activeSessionStateObs in the tree-update autorun
- agentService: dedupe _ skip setSessionMeta when theattachGitState
  newly computed git state equals the current _meta.git, avoiding action
  churn after every turn

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Apr 26, 2026
@pull pull Bot added the ⤵️ pull label Apr 26, 2026
@pull pull Bot merged commit 1fa1b7a into code:main Apr 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants