* Agents mobile (prototype): phone-aware dialog handler
Render IDialogService confirm/prompt as native bottom sheets on phone
layout, covering every modal in the Agents window in one place instead of
converting call sites individually.
The Agents window now registers a MobileDialogHandlerContribution (in place
of the standard web dialog handler) that drains the shared dialog model
through a MobileAwareDialogHandler. That handler delegates to the standard
BrowserDialogHandler off-phone and for text input / about, and on phone maps
the dialog model (message + detail + ordered buttons + optional checkbox)
onto a bottom sheet via the existing showMobileContentSheet primitive:
full-width stacked buttons at 44px, the safe action styled secondary, focus
moved into the sheet and restored on close.
Because confirmations from the sessions layer AND inherited chat / workbench
dialogs all flow through one handler, this covers them uniformly (e.g. delete
session, delete chat, discard changes, sign out) without per-call-site work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Agents mobile: drop the bespoke archive confirm sheet
The phone-aware dialog handler now renders every IDialogService.confirm as
a bottom sheet on phone, so the per-call-site mobileArchiveConfirmSheet is
redundant. Collapse ArchiveSectionAction back to a single dialogService.confirm
(matching the group "Mark All as Done" action) and delete the bespoke helper.
This also brings the section archive-all in line with the group archive-all:
both now flow through the handler and get the same sheet on phone, including
the "Do not ask me again" checkbox (safe to surface now that it renders in a
proper bottom sheet rather than the previously cramped desktop dialog).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: live checkbox state + dialog aria-describedby
- Track the chosen button and checkbox state across the whole interaction so
a backdrop / Escape dismiss (or a checkbox toggle) returns the live values
instead of a snapshot captured only on button click.
- Wire the message / detail to the sheet's role="dialog" element via
aria-describedby so screen readers announce them when the sheet opens.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: open the session when using Open in VS Code
The "Open in VS Code" action in the Agents window only opened the
session's workspace folder, leaving the user to manually open Chat and
click into the session. Hand off the active session resource through a
new chatSessionToOpen open-window option so the opened window restores
both the folder and the session via the existing vscode:openChatSession
IPC, matching the web variant that already forwards the session.
Fixes#323200
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: address CCR feedback for Open in VS Code session handoff
Only send vscode:openChatSession when exactly one window is opened so the
session handoff is not sent to an ambiguous target, and condense the
inline handoff comment to a single line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(automations): add session runner — dispatches automation runs via sessions layer
Implements AutomationRunner:
- Creates sessions with configured prompt, mode, model, and permission level
- Dispatches via createAndSendNewChatRequest (background session)
- Tracks run lifecycle: recordRunStart/updateRun on IAutomationService
- Error notification on run failure
- Cancellation handling for mid-flight stops
- Sessions titled with automation name via renameChat
Supporting changes:
- ICreateNewSessionOptions: added modelId, modeId, permissionLevel, isolationMode, branch
- ISessionsProvider: added optional setMode/setPermissionLevel/setIsolationMode/setBranch
- ISendRequestOptions: added optional source field
- createAndSendNewChatRequest now returns ISession | undefined
- Implementation applies createOptions to provider before sending
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(automations): title the session before the agent host launches
Thread an optional title through ISendRequestOptions and apply it in
_sendFirstChat before sending, falling back to the query's first line.
The automation runner passes the automation name, so launch-time prompts
(e.g. worktree file transfer) show the automation name instead of the
prompt text. The post-send renameChat still sets the committed CLI title.
* Signing commit
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Support multiple chats for Claude agent-host sessions
Enable a single Claude (agent-host `provider === 'claude'`) session to own
multiple peer chats in the Agents window, matching the Copilot CLI experience.
- ClaudeAgent: add `_chatSessions` map plus `createChat` / `disposeChat` /
`getChats`, per-chat persistence, lazy resume of restored peer chats, and
per-chat routing on `sendMessage` / `abortSession` / `changeModel` /
`changeAgent`. Fork a peer chat from a source chat's SDK conversation at a
turn, falling back to a fresh chat when the fork anchor can't be resolved.
- Agent-host sessions provider: advertise `supportsMultipleChats` for the
`claude` logical session type in addition to `copilotcli`.
- Update SESSIONS.md and ClaudeAgent tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: fix Claude peer-chat signal routing and harden multi-chat lifecycle
Fix additional (peer) chats in Claude agent-host sessions getting stuck in
progress: a peer chat passes its `ahp-chat` channel URI as the session's
`sessionUri`, but `ClaudeAgentSession` derived its routing channel via
`buildDefaultChatUri(sessionUri)`, double-encoding it so the renderer never
matched the channel. Use the chat URI directly when `sessionUri` is already an
`ahp-chat` channel.
Also harden the peer-chat lifecycle per code review:
- serialize all catalog read-modify-write on the parent session id (createChat /
disposeChat / _updateChatCatalogModel) to avoid lost updates
- hold the per-chat lock across both materialize and send so disposeChat /
disposeSession serialize against an in-flight turn (no use-after-dispose)
- make _disposeChildChats async + per-chat serialized to avoid zombie entries
- abort provisional peer chats up front during shutdown
- route setPendingMessages steering to peer chats
- shape-guard the persisted catalog model
Refs https://github.com/microsoft/vscode/issues/322776
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: thread chat channel through setPendingMessages for peer-chat steering
Address CCR feedback: peer-chat steering was non-functional because
`AgentSideEffects._syncPendingMessages` always dispatched the parent session URI
to `agent.setPendingMessages`, so the Claude peer-chat routing branch was never
reached and steering landed on the default chat.
Add an optional `chat?` param to `IAgent.setPendingMessages` (mirroring
sendMessage/abortSession/changeModel), dispatch the chat channel from
`_syncPendingMessages` (undefined for the default chat), and route via it in
ClaudeAgent. Copilot/Codex 3-param implementations remain valid and unchanged.
Adds an AgentSideEffects dispatch test asserting the peer chat URI is forwarded
as the `chat` arg (and is undefined for the default chat).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: unify Claude session/peer-chat plumbing into one entry container
Address review feedback (connor4312): stop overloading session/chat URIs and
the parallel-map split that special-cased peer-chat dispatch.
- ClaudeAgentSession now takes an explicit `chatChannelUri`; its `sessionUri`
is always the real session URI and is never a chat URI
(`isAhpChatChannel(sessionUri)` can no longer be true). Per-chat resources
(db, overlay, config scope, server-tool advertise) key off a derived
`_storageUri` so peer chats stay isolated without overloading `sessionUri`.
- Drop the parallel `_chatSessions` map: a single `_sessions` map of
`ClaudeSessionEntry` containers now holds each session's default chat plus its
peer chats. Dispatch resolves a chat via `_findChat(session, chat)` / the
entry, and teardown disposes the whole entry (main + peers) via
`_teardownEntry`.
- Unify peer-chat message reconstruction with `getSessionMessages` via a shared
`_reconstructTurns(sdkId, routingUri, primeOn)`; remove the duplicated
`_getChatMessages`.
No behavior change to storage keying (main -> session URI, peer -> chat URI).
All ClaudeAgent / AgentSideEffects / CopilotAgent / AgentService node tests pass
(425 claude tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: add opaque providerData to chat catalog + multi-chat tests
Wave A + gate G-B1 of the multi-chat unification:
- AgentHostStateManager: add an opaque, agent-owned `providerData?: string`
to peer-chat catalog entries (addChat/restoreChat) plus getChatProviderData.
Stored verbatim and never parsed; the default chat carries none. This becomes
the single source of truth for a peer chat's backing-conversation token,
replacing the agents' private copilot.chats/claude.chats persistence.
- Add characterization tests for the StateManager catalog (default chat, add/
remove/restore, summary roll-up) and peer-chat + restore round-trip tests for
CopilotAgent and ClaudeAgent, guarding the upcoming de-dup waves.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: orchestrator owns the peer-chat catalog (Wave B de-dup)
Make AgentHostStateManager's catalog the single source of truth for peer
chats, removing the agents' private copilot.chats/claude.chats persistence:
- agentService: restore peer chats by enumerating the orchestrator's own
catalog (using the opaque providerData blob) instead of agent.getChats;
call materializeConversation(chatUri, providerData) before getSessionMessages
so the agent re-attaches its conversation backing; persist providerData on
createChat and re-persist on onDidChangeConversationData.
- IAgent: createChat returns IAgentCreateChatResult { providerData? };
add materializeConversation + onDidChangeConversationData.
- CopilotAgent / ClaudeAgent: stop writing their private *.chats catalogs;
shrink _chatSessions to a live-only map; decode providerData to rebuild the
chatUri -> sdkSessionId mapping; emit onDidChangeConversationData on per-chat
model/fork change. A one-time legacy *.chats READ (triggered by an undefined
providerData blob) migrates in-flight sessions.
Typecheck, valid-layers-check, and the agentService/Copilot/Claude/StateManager
suites (511 tests) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: add scope/conversation IAgent surface + dispatch mapper (gate G-C1)
Introduce the orchestrator-owned scope/conversation vocabulary on IAgent,
additively alongside the legacy (session, chat?) surface (kept as a compat
shim until waves C2-C5 migrate each agent):
- IAgent: add createScope/disposeScope and an IAgentConversations surface
(createConversation/disposeConversation/getMessages/fork, conversation-
addressed sendMessage/abort/changeModel/changeAgent).
- AgentService: map feature-level (session, chat) -> (agent, scope,
conversation) and own default-chat resolution; resolveConversationUri helper.
Typecheck, valid-layers-check, and the AgentService dispatcher suites
(112 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: agents adopt scope/conversation surface (Wave C)
CopilotAgent, ClaudeAgent and CodexAgent now implement the new scope/
conversation IAgent surface (createScope + conversations:
createConversation/disposeConversation/getMessages/fork and conversation-
addressed sendMessage/abort/changeModel/changeAgent), and agentSideEffects
threads it through where straightforward. The legacy (session, chat?) compat
shim is intentionally retained for now; it is removed centrally in gate G-C2.
Typecheck, valid-layers-check, and the Copilot/Claude/Codex/AgentService
suites (563 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: remove legacy (session, chat?) shim from IAgent (gate G-C2)
With every agent migrated to the scope/conversation surface (Wave C), drop
the agent-facing legacy methods — sendMessage(session,chat,...)/createChat/
disposeChat/getChats and the chat?-suffixed abort/changeModel/changeAgent —
leaving only the conversation-addressed surface on IAgent. AgentService,
agentSideEffects and the three agents migrate their remaining call sites; the
mock agent is updated to the new surface. The orchestrator-facing
IAgentService/IAgentConnection (session,chat) API and the wire protocol are
unchanged — they remain the (session,chat) -> conversation mapping boundary.
Net -209 lines. Typecheck, valid-layers-check, and the Copilot/Claude/Codex/
AgentService suites (559 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: add harness spawn-conversation channel + catalog routing (gate G-D1)
Generalize the subagent_started/subagent_completed signals into a first-class
membership channel: IAgent.onDidSpawnConversation({ scope, conversation,
parent? }) / onDidEndConversation(conversation). AgentService subscribes on
provider registration and routes spawned conversations straight into the
chat catalog (addChat/removeChat), so harness-spawned chats (teams, fleet,
subagents) and user-driven chats share ONE catalog path, preserving the
parent relation. Per-agent emission of these events lands in Wave D.
Typecheck, valid-layers-check, and the AgentService suite (107 passing) pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: agents emit spawn events + capability-driven UI gating (Wave D)
- CopilotAgent / ClaudeAgent emit onDidSpawnConversation/onDidEndConversation
from their subagent/fan-out paths, so harness-spawned chats flow into the
shared catalog via the G-D1 channel (carrying the parent relation).
- IAgentDescriptor advertises IAgentCapabilities { supportsMultipleChats,
supportsFork, supportsTeams }; the agent-host sessions provider maps these
onto ISessionCapabilities instead of the hardcoded
supportsMultipleChats(logicalSessionType) session-type check, and exposes
supportsFork/supportsTeams context keys so UI gates generically with no
per-harness branches.
Typecheck, valid-layers-check, and the agentHost + sessions provider suites
(1725 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: test default-chat rename is restored on restoreSession
Re-add coverage for restoring a default chat's independently-persisted custom
title (customChatTitle:<defaultChatUri>), homed in the dedicated restoreSession
suite using the localService + TestSessionDatabase pattern. A version of this
test arrived via a merge but was misplaced in the createChat suite; this puts it
in the right place. The behavior itself lives in AgentService.restoreSession.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unused supportsTeams capability
The supportsTeams capability was fully plumbed (protocol to agents to
ISessionCapabilities to SessionSupportsTeamsContext) but had zero
consumers: no when-clause and no widget read it. Harness-spawned
teams/subagents surface automatically via onDidSpawnConversation
regardless of any flag, so this was speculative dead weight. Remove all
13 references across 9 files. supportsMultipleChats and supportsFork are
left untouched as they are actually consumed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: unify subagent catalog membership onto the spawn channel (DR1)
Make the spawn-conversation channel the single owner of subagent catalog
membership, removing the duplicate add path:
- AgentSideEffects._handleSubagentStarted no longer calls addChat; it keeps
only the subagent lifecycle (ChatTurnStarted, _subagentChats tracking, parent
tool-call Subagent content, buffered-signal drain, teardown).
- AgentService now sequences a subagent_started/subagent_completed signal onto
the spawn-channel handlers (_onConversationSpawned/_onConversationEnded) via a
new onDidSessionProgress subscription registered BEFORE the side-effects
progress listener. This deterministically guarantees the subagent chat exists
in the catalog before its turn is started, independent of when the agent
registers its own subagent->spawn bridge (addChat/removeChat are idempotent).
- Extract the subagent-signal -> spawn-event mapping into shared helpers
(subagentSpawnConversationEvent/subagentEndConversation) reused by the agents'
bridges and the AgentService sequencer.
Adds a "subagent membership sequencing" suite: exactly one catalog entry with
parent origin/title/started turn regardless of order, buffered inner-signal
drain, and completion teardown. Typecheck, valid-layers, and the agent suites
(567 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: add multi-chat architecture spec
Living architecture spec for the agent-host multi-chat design (scope/session vs
conversation/chat, orchestrator-owned catalog, opaque providerData, unified
spawn channel, capability gating) with mermaid diagrams. Kept in sync with the
implementation, like SESSIONS.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: legacy peer-chat migration (BC1) + Copilot session container (F2)
Two changes to the Copilot/Claude agents and the orchestrator restore path:
BC1 - backward-compatible restore of legacy peer chats: sessions whose
additional chats were persisted only in the old agent-owned copilot.chats /
claude.chats format (no orchestrator peerChats catalog) previously restored
with those chats invisible. AgentService now performs a one-time migration when
the orchestrator catalog is absent (undefined, not []): it enumerates the
agent's legacy chats via a new migration-only IAgent.listLegacyChats, restores
them through the normal catalog path, and writes the peerChats key so the drain
runs once. Fixes the stale JSDoc that claimed a fallback removed in G-C2.
F2 - collapse CopilotAgent's default-vs-peer _sessions/_chatSessions two-map
split into a single _sessions map of a CopilotSessionEntry container (mirroring
ClaudeSessionEntry): the entry holds the default chat plus a nested _peerChats
map. Removes the special-casing Connor flagged on #323625.
Typecheck, valid-layers-check, and the AgentService/Copilot/Claude suites
(570 passing, incl. the migrate-once / empty-catalog / new-format restore
cases) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: rename agent scope/conversation surface to session/chat (N1)
Collapse the agent-facing vocabulary back to session/chat, so the whole stack
(protocol, orchestrator, UI, agents) speaks one language. The scope/conversation
terms were a 1:1 veneer over concepts already named session/chat elsewhere (the
create* methods even returned IAgentCreateSessionResult). The sessionUri vs
chatChannelUri TYPE separation is preserved — this is a naming change only.
- IAgent: createScope/disposeScope -> createSession/disposeSession; the
conversations surface (IAgentConversations) -> chats (IAgentChats) with
createChat/disposeChat/getMessages/fork + conversation-addressed send/abort/
changeModel/changeAgent now chat-addressed; materializeConversation ->
materializeChat; onDidSpawn/End/ChangeConversation* -> onDidSpawn/End/ChangeChat*.
- Types: IAgentSpawnConversationEvent -> IAgentSpawnChatEvent,
IAgentConversationDataChange -> IAgentChatDataChange; drop
IAgentCreateConversationOptions (reuse IAgentCreateChatOptions).
- Helpers: resolveConversationUri -> resolveChatUri and the private
_*Conversation* members across AgentService/agents renamed to _*Chat*.
- IAgentService/IAgentConnection/protocol/UI names unchanged (already session/chat).
- Reconcile agentSideEffects tests to the renamed chat surface (mock URI
normalization) and update MULTI_CHAT_ARCHITECTURE.md terms/diagrams.
Typecheck, valid-layers-check, and the agent suites (686 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: align architecture diagram label with chat terminology
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: align multi-chat spec terminology with the session/chat rename
Refine MULTI_CHAT_ARCHITECTURE.md wording after N1: the default chat's backing
SDK *session* (not "SDK chat") is the session, peer chats are backed by their
own sdkSessionId, and clarify the (session, chat) -> (agent, session URI, chat
URI) mapping label and the per-chat state description.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: group refactor-added helpers into logical units (NS1)
Reduce loose top-level exports in common/agentService.ts introduced by the
multi-chat refactor (the pre-existing config/env helpers are left untouched):
- Move resolveChatUri to common/state/sessionState.ts next to its sibling
chat-URI helpers (buildChatUri/buildDefaultChatUri/isDefaultChatUri/
parseChatUri) — its logical home.
- Group the subagent signal -> spawn-channel mappers into an
`export namespace SubagentChatSignal { toSpawnEvent, toEndChat }` (mirroring
the existing AgentSession namespace), updating the Copilot/Claude bridges and
AgentService._sequenceSpawnedChat call sites.
Pure move/regroup, no behavior change. Typecheck, valid-layers-check, and the
agent suites (686 passing) all pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make ISession.capabilities observable so late-hydrating capabilities reconcile
The agent-host adapter exposed `capabilities` as a live plain getter reading
the connection's root state. When `rootState.agents[].capabilities` hydrated
after a session's first `SessionState`, existing sessions were never
reconciled: a multi-chat catalog processed while `supportsMultipleChats` was
still `false` stayed collapsed to `[defaultChat]`, and the
`supportsMultipleChats`/`sessionSupportsFork` context keys stayed stale because
a plain getter cannot be tracked by the `setActiveSessionContextKeys` autorun.
Change `ISession.capabilities` to `IObservable<ISessionCapabilities>`. The
agent-host adapter derives it from `connection.rootState` (bridged via
`observableFromEvent`) with `derivedOpts` + `structuralEquals`, and re-applies
the last `SessionState` catalog in an autorun when capabilities change. Static
providers wrap their capabilities in `constObservable`; consumers read
`.read(reader)` (context keys) or `.get()` (one-shot).
Adds a regression test and updates SESSIONS.md and the sessions skill.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Keep completed subagent chats live (fix subagent integration tests)
A DR1 regression conflated 'subagent turn completed' with 'chat removed':
a subagent_completed -> removeChat path tore the child subagent chat out of
the catalog on completion, so subscribing to it after the parent turn
completed failed with 'Resource not found'. A completed subagent chat must
stay live and subscribable (merely hidden from listSessions), with its turn
completed via AgentSideEffects.completeSubagentSession; subagent chats are
removed only on session teardown via removeSubagentSessions.
- agentService._sequenceSpawnedChat: handle spawn only (no removal on completion)
- copilotAgent/claudeAgent spawn bridges: stop firing onDidEndChat on completion
- remove now-unused SubagentChatSignal.toEndChat (keep toSpawnEvent)
- keep onDidEndChat as a generic membership-removal hook
- tests: assert the subagent chat survives subagent_completed and that
completion does not fire onDidEndChat
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add non-opaque backingSession to IAgentCreateChatResult
Introduces a first-class, non-opaque backingSession URI on the peer-chat
create result so the orchestrator can correlate and suppress a peer chat's
backing SDK session. Kept distinct from the opaque providerData blob so the
providerData opacity invariant is preserved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Report peer-chat backingSession from Claude and Copilot agents
ClaudeAgent._createChat mints a fresh top-level SDK session per peer chat in
the same store its listSessions enumerates, so it now returns that session as
backingSession for the orchestrator to suppress. CopilotAgent sets it too for
uniformity (harmless — its peer sessions already don't leak).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Filter peer-chat backing sessions from the top-level session list
createChat now stamps a persisted peerChatBacking marker into the backing
session's database, and listSessions drops any enumerated session carrying it
(batched into the existing metadata overlay, mirroring the subagent filter).
Fixes Claude peer chats leaking as separate top-level sessions. Adds a unit
test covering the filter and its persistence across a restart, plus doc
invariant I7.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Share peer-chat scaffolding across Claude & Copilot agents
Extract the near-verbatim multi-chat peer scaffolding shared by the Claude
and Copilot agents into a new node-target module
`src/vs/platform/agentHost/node/agentPeerChats.ts`:
- Move the opaque `providerData` codec (`IPersistedChat`,
`encodeProviderData`, `decodeProviderData`) into the shared module and
export it. Use Claude's stricter `model` validation, which is a superset
of Copilot's unconditional cast. Both agents import it and drop their
private copies.
- Add a generic `AgentSessionEntry<TSession extends IDisposable>` container
holding the optional default session plus the peer-chat map. Rewrite
`CopilotSessionEntry` as an empty subclass and `ClaudeSessionEntry` as a
subclass that narrows `session` to non-optional.
Behavior-identical refactor; existing Agent* suites stay green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Persist migrated legacy peer chats in a single atomic catalog write
`_migrateLegacyPeerChats` wrote the migrated peer chats to the orchestrator
catalog one entry at a time in a loop. Each `_persistPeerChat` is a separate
read-modify-write of `PEER_CHATS_METADATA_KEY`, so after the first write the key
is present containing only the first entry. If the agent-host process crashed
(OS kill, power loss, forced restart) after write 1 but before write N, the
catalog was left partial; on the next restart `_readPersistedPeerChatCatalog`
returns that subset (not undefined), the catalog-present branch short-circuits,
and migration never re-runs -- chats 1..N-1 are lost forever.
Write the whole migrated set in a single atomic `_enqueuePeerChatCatalogWrite`,
so the key is absent before and complete after; no partial catalog can survive a
crash mid-migration. Adds regression tests asserting the full set is persisted in
one write and that a rejected write leaves the key absent (never a subset).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Customizations section in the Agents sidebar always reset to expanded
on reload because AICustomizationShortcutsWidget initialized _collapsed to
false and never persisted the state.
Read and store the collapsed state via IStorageService (StorageScope.PROFILE)
so the section restores its collapsed/expanded state on reload. Apply the
persisted state to the DOM after each render instead of hard-resetting to
expanded.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-enables the 'Test Codex session' smoke test (skipped in #323867).
The native Codex binary ships as a per-platform *optional* dependency of the
`@openai/codex` launcher shim; npm silently ignores an optional dep that fails
to install, so a node_modules cache built during such a hiccup keeps the shim
(which makes Codex report as "available") but loses the binary — the session
then fails at spawn time and the test only times out after 5 minutes.
Add a from-source pre-check that fails fast with an actionable message when the
shim is present but the native binary is missing. No cache change is needed
here (main's lockfile has since rolled to a healthy cache); if it recurs,
bumping build/.cachesalt forces a fresh npm ci that reinstalls the binary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
agentHost: show background agents as complete
Use completion-oriented copy for agent_idle system notifications and update the direct expectations.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restores pasting files copied from an external OS file explorer into the
Explorer, which regressed in 1.127.0 (#323858).
The guard added by #323002 read `event.clipboardData.files` after awaiting
`clipboardService.hasResources()`. The clipboard `DataTransfer` is only valid
during synchronous paste-event dispatch, so after the await the file list was
empty and external pastes did nothing. The guard was only needed to counter the
double-paste from #320685 (native OS file writes); this reverts to the simple
listener on main.
Not reverted on release/1.127 since that release already shipped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
chat: warn when changing model/options mid-session breaks the prompt cache
The model and options pickers surface a cache-break cost hint when the
chat session's prompt cache is warm — switching the model or changing
options mid-session resets that warm cache and may increase cost. The hint
includes a "Learn more" link (rendered via the shared Link widget through
a new optional headerLink on the action list header banner) pointing at
the Copilot docs on optimizing AI usage.
Warmth is derived directly from the session's request history at the
picker rather than tracked in a parallel in-memory map: getRequests()
length for the default chat, and session status leaving Untitled for
agent-host sessions. Both warm as soon as the first request is sent, so
the signal is drift-free and consistent across surfaces — it covers
reloaded/restored sessions, every request path, and the agents window.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update @github/copilot and @github/copilot-sdk to version 1.0.67 and 1.0.5-preview.1 respectively
- Updated package.json and package-lock.json in both root and remote directories to reflect the new versions of @github/copilot and @github/copilot-sdk.
- Modified copilotSessionLauncher.ts to include new hooks in the Copilot session configuration.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* utils: document binarySearch
* nes-datagen: generate training data from continuous recordings
Continuous enhanced telemetry now ships sliding-window recordings that, unlike per-request alternative-action recordings, carry no requestTime. The datagen pipeline needs a point to split each recording into edit history before/after, so this adds a pluggable pivot strategy (starting with Random, selectable via --pivot-strategy) and a new continuous/ pipeline module that replays a recording at the chosen pivot to produce a processed row.
Along the way this consolidates the pipeline's error and index handling: a shared WithRowIndex<T> replaces the ad-hoc { originalRowIndex, ... } pairs, per-record processing returns Result<IProcessedRow, Error> instead of field-presence unions, and failures surface as original Error objects (no string round-tripping). The telemetry sender's continuous payload is now the documented IContinuousRecording type.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* nes-datagen: label alt-action replay errors by originalRowIndex
Address PR review: the alternative-action path mislabeled diagnostics when
earlier records failed to parse.
- processAllRows: push replay errors with the row's true `originalRowIndex`
instead of its position in the filtered `rows` array (parse failures make
`rows` sparse, so the two diverge).
- loadAndProduceProcessedRows: resolve `languageForRow` via an
`originalRowIndex`-keyed Map rather than positional `rows[i]`, matching how
callers pass `e.originalRowIndex`.
- Clarify the `recordCount` doc: it counts successfully-parsed records (parse
failures are counted separately in `parseErrors`).
- Add a regression spec asserting replay errors carry the row index, not the
array position.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* sessions: animate input banner border while an action is running
The CI checks and comments input banners in the Agents window now show an
animated "border beam" while an async action is running, mirroring the chat
input's in-flight border. This is primarily for the CI "Fix Checks" action,
which spends a noticeable amount of time fetching check annotations before
submitting the prompt.
- SessionInputBannerWidget: action run() may return a promise; while it is
pending the banner gets a `.working` class and its buttons are disabled. The
animation is only shown after a 50ms delay so fast actions don't flicker.
- CSS: added the comet/glow ::before/::after rings (padding + inverted mask
trick) driven by a spinning conic gradient, respecting reduced-motion.
- Color matches the chat input working-border token for comments banners, and
the orange accent (same as the primary button) for CI banners.
- Fix Checks / Address Comments await their work; Reveal Checks stays
fire-and-forget. Added a CIFailuresLoading screenshot fixture.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: address input banner loading review feedback
- Disable banner buttons immediately while an action is pending; only the
animated border is delayed by 50ms to avoid flicker.
- Make the click handler explicitly fire-and-forget and swallow action errors
inside _runAction so it can never produce an unhandled rejection.
- Type _executeCommand cleanly as Promise<void> (await + try/catch) instead of
returning the unknown-typed executeCommand result.
- Expose SessionInputBannerWidget.setWorking so the CIFailuresLoading fixture
reflects the real disabled-button state, not just the .working class.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* build: register session input banner CSS variables for hygiene
The animated banner border introduced new CSS custom properties that must be
listed in build/lib/stylelint/vscode-known-variables.json, otherwise the
hygiene 'Unknown variable' check fails.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Render addComment feedback tool call as a rich reference in chat
Show the agent-host `addComment` tool call as a comment-icon reference with the
tool name and a quoted 20-char preview of the comment body. Clicking it reveals
the comment (agent feedback) in the editor via a new `_agentFeedbackReview.revealAt`
command that resolves the owning session from the file resource.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review feedback
- RevealAt: prefer getSessionForFile (falls back to active session for
in-scope files) before getMostRecentSessionForResource, so the rendered
reference works before the resource has accumulated feedback.
- isOneBasedRange: enforce integer, >= 1 coordinates so invalid ranges fall
back to the server-authored message.
- Use import type for IMarkdownString in the test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix compile error: narrow out Streaming state before reading toolInput
ToolCallState is a union whose Streaming member has no `toolInput`
(it lives on ToolCallParameterFields). Guard addCommentReference on
status !== Streaming so the property access type-checks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix eslint no-duplicate-imports for range.js
Merge the value import of Range and the type-only import of IRange
into a single import statement.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: rename agent feedback Remove button to Delete and refine tooltips
Rename the secondary action button in the agent feedback editor widget from 'Remove' to 'Delete' and add source-aware tooltips for the Accept/Delete buttons (PR comments vs agent comments).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: rename removeButton variable to deleteButton
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix blocked agent host sessions from nested subagent client tools
Nested (depth >= 2) subagents were never observed by the renderer, so a
client-owned tool called deep in the subagent tree (e.g. the `problems`
tool via a subagent-of-a-subagent) never ran and the session hung in
"Input Needed" with no visible prompt.
Two root causes, fixed on both sides:
- Renderer: `tryObserveSubagent` required the subagent-discovery content
block before subscribing to a child chat. It now observes as soon as the
tool is a known subagent-spawning tool that is running, deriving the child
chat URI from the tool id alone — robust and depth-independent.
- Agent host: `subagent_started` carried no parent context, so the discovery
content block was dispatched on the top-level chat instead of the immediate
parent subagent chat (a no-op there). Threaded `parentToolCallId` through
the signal (copilot + claude adapters) so the block routes to the immediate
parent chat via a flat one-hop lookup at any nesting depth.
Also keeps producing the session-level `inputNeeded` state for other clients
(with tests), though the renderer no longer consumes it.
Fixes#323738
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix test type narrowing and clarify subagent observation comment
- Narrow ToolCallState to Running before reading .content in the 3-level nested routing test (fixes Compile & Hygiene TS error).
- Reword the tryObserveSubagent comment to frame content-block-less observation as robustness (older/misrouting hosts, restored snapshots) rather than current agent-host behavior, which this PR fixes. Addresses Copilot review feedback.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix flaky wait in nested subagent tests
The level-2 and content-block-less nested subagent tests polled for the client tool's local invocation, then immediately asserted its completion was dispatched — but completion lands one or more microtasks later, and deeper nesting adds async hops, so it raced on slower CI (Webkit). Poll for the dispatched ChatToolCallComplete instead (implies invocation already happened). Verified locally with runTests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Idle-session eviction in `_maybeEvictIdleSession` is causing issues where
cached session state is dropped while clients still expect it to be
observable. Disable the eviction path for now while we investigate, and
update the related tests to expect state to remain cached after the last
subscriber drops.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: keep chat tab bar visible for closed chats and diverged title
The chat tab strip in the Agents window header previously disappeared once
only a single open chat remained, hiding closed-but-reopenable chats and any
chat whose title had diverged from the session title.
Tab-strip visibility is now a single shared observable
`IActiveSession.shouldShowChatTabs`: shown when the session has more than one
chat (counting closed, non-tool chats) or its single remaining chat's title
diverged from the session title. Both the chat composite bar and the renamed
`SessionShouldShowChatTabsContext` context key (was
`SessionHasMultipleOpenChatsContext`) read this observable, keeping the
header/strip New Chat + Conversations split in sync.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: address CCR feedback on chat tab bar visibility
- Scope chat-to-chat navigation (next/previous chat, Ctrl+Tab switcher) on a
dedicated SessionHasMultipleOpenChatsContext (more than one open tab) instead
of the broadened SessionShouldShowChatTabsContext, so those chords stay a
no-op (and fall back to session navigation) when only a single open chat
remains (diverged-title single chat, or one open + one closed chat).
- Add a test asserting shouldShowChatTabs stays true while visibleChatTabs drops
to 1 after closing a non-main chat.
- Tighten the LAYOUT.md visibility wording and compress over-long inline comments.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: add shouldShowChatTabs to chatCompositeBar fixture mock
The component fixture's mock IActiveSession was missing the new
shouldShowChatTabs observable that ChatCompositeBar now reads, causing every
chatCompositeBar fixture to fail rendering ("Cannot read properties of
undefined (reading 'read')"). Add it as a derived mirroring production.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(automations): add foundation — types, storage service, scheduler, leader election
Introduces the Automations subsystem core:
- IAutomation types and IAutomationSchedule (interval-based: manual/hourly/daily/weekly)
- DST-safe computeNextRunAt scheduling logic
- IAutomationService with persistent JSON ledger (StorageScope.APPLICATION)
- Optimistic concurrency via revision counter for cross-window safety
- Leader election to ensure single scheduler instance across windows
- AutomationScheduler: periodic tick loop checking due automations
- Feature gate: chat.automations.enabled setting mirrored to context key
- Unit tests for scheduling logic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address Copilot Code Review feedback on PR 1
- Add _unsupportedSchema guard to recordRunStart and updateRun
- Localize CRASH_RECOVERY_REASON string via nls.localize()
- Move imports above registerSingleton call in contribution file
- Fix test comment referencing wrong helper name (buildLocalDate -> localDate)
* fix: address CCR round 2 — schema guards, telemetry cleanup, scope fix
- Add _unsupportedSchema guard to markStaleRunsFailed and advanceNextRunAt
- Replace boolean telemetry flags with actual enum values (permissionLevel, isolationMode)
- Remove hasProviderId, hasSessionTypeId, hasModelId, hasMode, hasPermissionLevel flags
- Add scope: ConfigurationScope.MACHINE to runTimeoutMinutes setting
* fix: gate scheduler construction on chat.automations.enabled
AutomationSchedulerCore (and its leader election heartbeat/storage
writes) are now only constructed when the feature setting is enabled.
Uses MutableDisposable for clean teardown on runtime toggle.
Addresses CCR feedback: avoid 30s heartbeat timer + storage writes
when the feature is disabled.
* fix: isolate per-automation errors in dispatch loop
Wrap advanceNextRunAt + runOneWithTimeout in try/catch so a failure
in one automation does not abort dispatch of remaining due automations.
* fix: register automations path in i18n resources
Adds vs/sessions/contrib/automations to the translation resources
so nls.localize calls pass the eslint translation-remind rule.
* fix: validate enum fields at write boundary and harden persist()
- Normalize permissionLevel/isolationMode in createAutomation and
mergeAutomation: reject unknown values to keep persisted data and
GDPR-classified telemetry low-cardinality.
- Wrap persist() storage read and write in try/catch so storage
failures degrade gracefully instead of breaking the scheduler.
* fix: scheduler to Eventually phase, simplify persist(), document serial dispatch
- Move scheduler registration from AfterRestored to Eventually —
background concern that doesn't need to block workbench restore.
- Remove re-read-before-write in persist() — refreshFromStorage via
onDidChangeValue already handles cross-window sync. Eliminates
redundant JSON.parse on every mutation and the misleading optimistic
concurrency check that overwrote anyway.
- Document that _pendingRuns serializes dispatch intentionally.
* style: remove em dashes and obscure acronyms from comments
Replace all em dashes with periods or semicolons. Remove TOCTOU
acronym. Replace multiplication sign with plain x.
* Signing commit
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: add browser-style chat tab keybindings (Cmd+T, Cmd+Shift+T, Delete)
Add three keybindings to the agents window that mirror browser tab management:
- Cmd/Ctrl+T: New Chat — opens a new chat within the active session, scoped
to sessions that support multiple chats and are not archived.
- Cmd/Ctrl+Shift+T: Reopen Last Closed Chat — reopens the most recently
closed chat tab. Tracks close order via a new _closedChatOrder array on
VisibleSession (append on close, splice on reopen), exposed as
lastClosedChat on IActiveSession, so reopen order is correct regardless
of original chat creation order.
- Delete / Cmd+Backspace (Mac): Delete Chat — permanently deletes the active
non-main chat with confirmation. Guarded by InputFocusedContext.toNegated()
so it never fires while the user is typing in the chat composer or any
other input field.
All three keybindings are scoped to IsSessionsWindowContext and
EditorAreaFocusContext.toNegated() so they only activate in the agents
window outside the editor area.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add lastClosedChat to IActiveSession test mocks
Three test files construct IActiveSession object literals that now need
the lastClosedChat property added after it was introduced to the interface.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: guard lastClosedChat against stale deleted chats; add MRU tests
- lastClosedChat now walks _closedChatOrder from newest to oldest and
skips entries whose chat has been deleted from the session (no longer
in session.chats or _closedChatUris), so Reopen Last Closed Chat
never no-ops on a stale reference after deletion.
- Adds 4 unit tests to 'VisibleSession - open/close chats' covering:
- MRU close-order correctness (close C then B → last closed is B)
- lastClosedChat updates after reopening the last-closed chat
- returns undefined when nothing is closed
- skips deleted chats and falls back to the next valid one
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add lastClosedChat to remaining IActiveSession test mocks
Two more test files (sessionsListModelService.test.ts and
sessionNavigation.test.ts) construct IActiveSession object literals
that were missing the lastClosedChat property.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>