[pull] main from microsoft:main - #1603
Merged
Merged
Conversation
* Initial plan * Show send spinner immediately on omni chat submit Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> * Fix routed chat submit feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> Co-authored-by: meganrogge <merogge@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirror Claude, Codex, and BYOK enablement through the experiment-aware renderer configuration layer instead of launch-time environment variables. Gate BYOK model publication dynamically and register providers when root configuration enables them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Browser: CDP proxy correctness fixes * feedback
* Initial plan * Fix omni chat opening duplicate window when adding context Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com>
Co-authored-by: vritant24 <13074644+vritant24@users.noreply.github.com>
TunnelAgentHostContribution depends on ITunnelHostService, which was only registered in the electron-browser layer. On web the contribution failed to construct with "depends on UNKNOWN service tunnelHostService", so tunnel discovery never started and the Agents window always showed the "Connect a host to get started" empty state, even with an online tunnel. - Adds WebTunnelHostService, an inert ITunnelHostService for the web target. Hosting a tunnel spawns the VS Code CLI, which a browser cannot do, so the service reports that it never shares. isTunnelHosted() then keeps every discovered tunnel visible in the picker. - Registers the service in sessions.web.main.ts before the contribution that consumes it. - Removes the isWeb workaround in remoteAgentHostActions.ts that existed only to avoid this gap. - Adds a regression test and records the cross-target requirement in the provider specification. (Commit message generated by Copilot)
* chat: add experiment hook to test Luna for dictation LLM cleanup
Adds a 'dictationLlmCleanupModel' assignment treatment so the LLM
dictation cleanup model can be flighted. Control keeps the existing
copilot-utility-small selector (gpt-4o-mini); the treatment value
gpt-5.6-luna selects Luna. The lookup shares the existing cleanup
deadline and preserves the raw-transcript fallback.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* copilot: publish hidden gpt-5.6-luna so dictation cleanup experiment can resolve it
The copilot vendor only publishes models to the workbench language-model
list when they are shown in the model picker (or are gpt-4o-mini). Luna is
not a picker model, so selectLanguageModels({ id: 'gpt-5.6-luna' }) would
return nothing and the dictation cleanup experiment would silently fall
back to the raw transcript. Add gpt-5.6-luna to the always-published
utility models so the treatment can resolve it while keeping it hidden
from the picker.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chat: harden dictation cleanup model experiment
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b8fa5c4e-601e-4267-87f4-366139bc420c
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b8fa5c4e-601e-4267-87f4-366139bc420c
Co-authored-by: vritant24 <13074644+vritant24@users.noreply.github.com>
…yling (#331337) * Initial plan * Size omni Add Context (+) button to match send button Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> * Match omni chat input editor background to window surface The floating chat input window is hosted under `.agent-sessions-workbench`, so the sessions stylesheet painted the input editor with `agentsChatInput.background` while the window surface used `input.background`, producing a visible color mismatch. Drive the editor background from the same live theme color as the surface via a new `--omni-input-editor-background` variable, and raise the rule so it wins over the sessions rule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> Co-authored-by: meganrogge <merogge@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Allow sandboxed access to terminal output files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix terminal sandbox service test environment --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dmitriy Vasyura <dmitriv@microsoft.com>
Restore the existing Claude and Codex enablement paths and keep the experiment-aware root configuration change scoped to BYOK models. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
agentHost: address discovery review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: vritant24 <13074644+vritant24@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix aquarium no showing
* Improve dictation cleanup reliability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d367acf7-ee13-488e-bf9f-4aef2d274190 * Preserve dictation cleanup timeout diagnosis Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d367acf7-ee13-488e-bf9f-4aef2d274190 --------- Copilot-Session: d367acf7-ee13-488e-bf9f-4aef2d274190
* update footer details * address comments
* mcp: preserve launch working directories across hosts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * mcp: address review and electron test failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * mcp: simplify provider working directory handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clear retained Codex runtime state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: address MCP discovery review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: adapt Codex routing test to thread inventory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: serialize Copilot chat discovery Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: deny disabled Claude MCP servers at startup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: preserve Copilot MCP cwd after re-enable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * MCP: address final cross-host review findings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Refactor browser sharing status into main process * Fix test
* agentHost: Report billed AI credits per turn Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Separate turn usage tracking Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agent Host changes for osortega/agents/chat-sticky-scroll-ux-review
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…330723) * Add multi-root session folder picker for agent-host sessions In a multi-root workspace, a new agent-host session must decide which workspace folder leads. This adds a harness-owned decision for the new-session Folder picker, seeded into the session `_meta` at creation and frozen as a creation-time fact across reload. The shared rule (folderPickerDecision.ts) counts folders that "qualify" under a provider-specific predicate: - 0 qualifying -> hide the picker, keep the current selection - exactly 1 -> hide the picker, pin that folder as primary - 2 or more -> show the picker so the user resolves the ambiguity Per-provider criteria (folder pins itself as primary when it carries config the provider only honors from the primary directory): - Copilot: recursive `.github/hooks/**/*.json` scan - Claude: `.mcp.json`, or a non-empty `hooks` block in `.claude/settings.json` / `settings.local.json` - Codex: `.codex/hooks.json` Client wiring resolves the decision into a picker-visibility update via a pure, unit-tested function (resolveFolderPickerDecisionUpdate) and gates the picker chip on a widget-scoped context key that defaults hidden, so it never flashes visible-then-hidden while the decision resolves. Adds unit tests for the shared decision, each provider's criteria, the meta round-trip, and the client-side resolver. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refactor folder-picker decision logic and enhance tests for session metadata handling * Enhance folder-picker agent tests with descriptor overrides for multiple working directories --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve explicit environment override precedence, gate BYOK session configuration as well as model publication, and register a null renderer channel when BYOK is unavailable. Add focused coverage without mutating the global configuration registry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* changes for sandbox toggle in copilot harness * fixing tests
* agenthost: speed up rename chat tool Avoid enumerating every session when resolving a rename target. Add a targeted registry lookup so rename validation and persistence remain awaited while lookup cost stays constant. Fixes #331110 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agenthost: share live session metadata overlay Reuse one helper for targeted session lookup and listSessions so both paths apply live state consistently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agenthost: make rename chat non-blocking Return to the agent before session lookup and persistence complete. Serialize queued renames and log deferred failures while retaining targeted session resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agenthost: update merged rename test Wait for the background rename independently while expecting the immediate tool result. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agenthost: simplify background rename Start each rename independently without a sequencer and keep the existing tool description unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Reduce automatic rename noise Mark reminder-driven rename calls so clients can hide successful administrative renames while keeping explicit renames and failures visible. Buffer leading model narration until the rename decision is known, discarding it on automatic rename and restoring it otherwise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Silence automatic rename output Return no model-facing text from the background rename request and hide marked automatic renames from their initial streaming state so they cannot contribute a transient activity title. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Distinguish automatic chat renames Add an automatic rename_chat argument so reminder-driven renames remain non-blocking while explicit renames await completion and surface failures. Keep the reminder and tests aligned with the execution contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Simplify automatic rename hiding Derive automatic rename presentation directly from the resolved tool input instead of tracking rename lifecycle state and buffering model actions in AgentSideEffects. Automatic renames may remain visible while their arguments stream, then hide once automatic:true is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Silence automatic rename narration Ask the model not to mention reminder-driven automatic chat renames while leaving explicit rename behavior unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Hide rename chat tool calls Follow the GitHub app's pragmatic presentation policy by hiding rename_chat invocations throughout their lifecycle. The automatic argument remains responsible only for non-blocking execution semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Forbid automatic rename narration Explicitly instruct the model not to say or mention the automatic rename before or after the tool call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Scope rename silence to automatic calls Clarify that rename narration should be suppressed only when the model invokes rename_chat with automatic: true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Limit rename PR to execution semantics Restore the existing rename presentation and reminder behavior. Keep this PR focused on passing automatic:true for reminder-driven calls and using it to choose non-blocking versus awaited execution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Align automatic rename reminder tests Update title-controller expectations for the concise automatic rename reminder. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Hide resolved automatic renames Use rename_chat tool input to hide successful automatic calls after arguments resolve while keeping streaming, explicit, failed, and cancelled calls visible. Align tests with the updated reminder wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Hide rename while input streams Hide rename_chat invocations by tool name during argument streaming, then reveal explicit calls or keep automatic calls hidden once input resolves. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Reuse rename tool name constant Use SessionServerToolName.RenameChat when matching bare and transport-prefixed rename tool calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Align rename reminder wording Update title-controller expectations for the stronger automatic rename narration instruction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Clarify automatic rename silence Tell the model to emit no text before an automatic rename and not mention it afterward. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Target automatic rename preambles Tell the model to invoke automatic chat renames silently without announcing the rename to the user. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Tighten automatic rename reminder Combine the title and silent-tool guidance into one concise automatic rename instruction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Forbid automatic rename preambles Make the automatic rename reminder explicitly require a silent tool call without a preamble. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Strengthen automatic rename reminder Update the automatic rename instruction and align its title-controller expectations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Polish automatic rename reminder Use grammatical user-facing wording in the automatic rename host instruction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-enablement Add BYOK enablement trace logs
* Initial plan * Confirm before discarding chat request edits Co-authored-by: justschen <54879025+justschen@users.noreply.github.com> * fix tets --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: justschen <54879025+justschen@users.noreply.github.com> Co-authored-by: justschen <justchen@microsoft.com>
* Initial plan * Fix markdown header rendering in omni bar routing badge preview Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> * Add test for flattened heading preview with more-content indicator Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> * Append preview ellipsis as text node to preserve bare-URL autolinks Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: meganrogge <29464607+meganrogge@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s Window (#331282) * Share one model-selection policy between Workbench chat and the Agents Window The Agents Window had its own implementation of "pick and remember the chat model". Its precedence engine, `transitionModelSelection`, lived in `workbench/contrib/chat/common/modelSelection.ts` but had exactly one production caller — `vs/sessions` — so the shared location bought nothing while the two surfaces were free to answer the same question differently. They did: a model restored onto an empty conversation was a user's choice to Sessions and mere spillover to Workbench, so `chat.defaultModel` overwrote it on one surface only. `SessionModelSelection` now expresses the Agents Window on top of `ChatInputModelSelectionController` through the `IChatInputModelSelectionRuntime` seam, and `transitionModelSelection` is deleted rather than relocated. Provenance becomes data instead of inference. `IChat.modelSource` records where a chat's model came from and `ISessionsProvider.setModel` requires the caller to state why it is setting one, so an automatic pick or a model a peer chat merely inherited can be told from a model the conversation is meant to run on. Only the latter outranks `chat.defaultModel`. `modelSource` is required rather than optional: an absent value reads as "the conversation's own", which is the answer that blocks the configured default, and a provider must not be able to claim it by saying nothing. A conversation's intended model is now held per conversation, keyed by the chat resource, so one chat's choice is unreachable from another by construction rather than by a scoping check. Both surfaces run a shared conformance matrix. Every scenario field is consumed through `conformanceInputs`, whose fields are all required, so an arm that stops reading one fails to compile instead of quietly asserting a different question. The matrix fences settled-catalog precedence and deliberately excludes publication lifecycle, where the two surfaces still differ on purpose: Workbench may display a stand-in while a model is pending, whereas Sessions waits rather than writing that stand-in through to a provider. Behaviour changes: - A model a new peer chat only inherited no longer blocks `chat.defaultModel` from seeding that chat. - Whether a conversation counts as empty is read from the chat rather than the session, so a brand-new peer chat in a finished session can still be seeded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Shrink the Sessions model-selection adapter to what only it can answer Three changes, none of which alter behaviour. The adapter restated the controller's own precedence. `_canProceedWhilePending` asked "may `chat.defaultModel` seed this conversation?" in terms of `ChatModelSource` and chat emptiness, while `applyConfiguredDefault` asked the same question in terms of selection reasons and pending intent. That is the drift this series exists to prevent, still present in the one place both surfaces have to agree. The controller now exposes `configuredDefaultToSeed`, which answers it once; the adapter supplies only the conversation's authority, because the model it would have to adopt to establish that authority is precisely the one still unpublished. Presentation moves to `sessionModelPickerState.ts`. What the picker shows is a different question from which model the conversation runs on, and two other modules already imported the option helpers from the selection file. The provider-to-controller vocabulary moves to `sessionModelProvenance.ts`, where the collapse from four `ChatModelSource` answers to two `ModelSelectionAuthority` ones can be read on its own. The adapter drops from 628 to 523 lines and is now about selection alone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Hold the Agents Window's model selection per conversation, not per input The intended model was already per conversation, but the two facts that describe it — whether the conversation has been seeded, and the authority behind its model — were still single fields on the input, kept correct by re-assigning them on every rebind. That is the arrangement the per-conversation intent was chosen to avoid: correct only for as long as each rebind path remembers to clear it, and silently wrong for the incoming chat the first time one does not. All three now live together in a `ConversationModelSelection` record held per chat resource, so one conversation's answer is unreachable while another is bound, by construction rather than by a reset. Clearing on rebind goes away with it: when no session is bound there is nothing to clear, because nothing that describes a conversation lives outside its own record. What remains on the input is a snapshot of the provider and the bound chat — models, model target, emptiness, binding identity — reassigned on every refresh rather than carried across passes. No behaviour change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Address review: key setModel by chat, and prune peer selections on the way out `ISessionsProvider.setModel` took only a session id, but the model belongs to a chat. Both providers had to guess which one: the Copilot provider resolved a grouped session id to the group's first chat, and the Agent Host consulted whichever session was globally active, falling back to the main chat. A picker shown in a visible peer chat could therefore write to a different conversation — the same model selection this series otherwise keeps strictly per chat, lost at the last step. It now takes the chat resource, matching `sendRequest`, and both providers resolve the chat instead of inferring it. Two agent-host tests had been setting the globally active session purely so the inference would pick their peer chat. They now name the chat, which is what they were testing all along. `_activeChatResource` remains for `setAgent`, which is keyed the same way and has the same weakness; it is documented as a guess so the next caller does not take it for an answer. Separately, peer chats' remembered model selections were pruned only on the multi-chat path. Removing the last peer takes a session down the single-chat path, which returned before reaching the prune, so those selections were never released. Pruning now happens before either branch returns, and only for peers this session had already materialized — a selection recorded for a chat whose state has not arrived yet is waiting, not stale. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Collapse the third model-provenance vocabulary into the reason it became Model selection carried three vocabularies for one question. A provider said where a chat's model came from (`ChatModelSource`: user, restored, inherited, automatic), the controller recorded how it applied one (`ModelSelectionReason`, whose `RestoredChoice` and `SessionRestore` are exactly "the conversation's own" versus "standing on it"), and in between sat `ModelSelectionAuthority`, a two-value type saying the same thing a third time. It was never anything else. `restoreReasonFor` existed only to turn an authority back into one of those two reasons, and all three of its callers assigned the result straight to the reason the controller actually keeps. Reading the flow meant translating between three type systems to follow one bit. `ModelSelectionAuthority` and `restoreReasonFor` are gone. The two entry points that took an authority now take a `RestoredModelReason` — the same two values, named in the vocabulary the controller already uses — so a caller states the reason it wants recorded and that is what gets recorded. Two vocabularies remain, which is the number the boundary needs: what a provider reports, and what the controller records. `sessionModelProvenance` translates between them in one place. No behaviour change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Apply a restored model under its own reason, and stop overloading "intent" A model the conversation chose but whose pool publishes it under another identifier was applied before the reason for applying it was recorded. Applying writes the model out through the runtime, and a surface that persists it reads the reason while that call runs — so the Agents Window wrote the conversation's own model under whatever reason the previous conversation left behind, which on a rebind is nothing at all. That lands in the provider as `Automatic`, reads back as spillover, and lets `chat.defaultModel` overwrite it: the exact failure this series exists to prevent. Every other apply site already set the reason first; this one is now consistent with them, with a test that asserts the reason in force at the moment the model is written. Three different things were called "intent": the conversation's intended model, the holder it lives in, and a programmatic selection waiting for its model to be published. Only the first two are the same idea. The third is now `_pendingProgrammaticSelection`, and its two byte-identical accessors — `hasPendingIntent` and `hasPendingProgrammaticSelection` — are one. `ModelSelectionReason.NoModels` was never assigned or compared; its only role was being excluded by `ModelSelectionApplyReason`. Both are gone, so the reason a model was applied has one type rather than two. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Ask the model-selection seam questions instead of Workbench's inputs `IChatInputModelSelectionRuntime` was shaped around Workbench's widget rather than around the question both surfaces ask. It took `location` and `getCurrentModeKind` so the controller could work out for itself whether a model was usable, which only Workbench has real answers for. The Agents Window filled the gap with four constants — `Ask`, `false`, `Disposable.None`, and an empty function — and a reader had to trace those through generic helpers to discover they meant "no restriction". The seam now asks directly. `isModelSupportedHere` and `getDeclaredDefaultModel` replace `location` and `getCurrentModeKind`, and each surface answers in its own terms: Workbench by its mode and where it is shown, the Agents Window by saying a session runs whatever its provider published. `subscribeToModelChanges` and `restoreModelConfiguration` are optional, because a surface that drives its own reconciliation and has no per-model configuration should omit them rather than stub them. Sessions' runtime now contains no stubs. `shouldResetModelToDefault` and `resolveModelFromSyncState` take that predicate instead of a context object, in the same order they checked before, so the surface-specific part is stated by the surface and the pool and session checks stay where they were. No behaviour change: `Ask` mode and the `Chat` location both short-circuit to true, so the constants the Agents Window used to pass already meant what it now says outright. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Make resetting to the default forget the preference it overrides `resetLanguageModelToDefault` cancelled any pending programmatic selection and selected the default, but left the conversation's intended model in place. That intended model is the remembered preference the reset exists to override, so the next time the catalog published, reconciliation restored it and the reset was silently undone. The `ConfiguredDefault` guard that would otherwise stop this does not apply, because a reset with no `chat.defaultModel` configured leaves the reason as `FirstAvailable`. Reachable from the automation dialog, which builds a fresh input whose intent is already seeded from the stored preference, then resets before applying the automation's own model. With no saved model to apply afterwards, the preference came back on its own. The controller now owns the whole operation as `resetToDefault`: abandon the pending selection, forget the intended model, take the default. A test asserts the default survives a subsequent catalog change; it fails without the forget. `clearPendingProgrammaticSelection` goes with it, having had no caller left. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * State the model-selection rule where the code implementing it lives The precedence this file owns was documented only in fragments, spread across the comments of the methods that enforce it. A reader could learn what each branch did without ever meeting the rule the branches exist to serve. The header now states it once: a model on a conversation is either that conversation's choice or spillover; `chat.defaultModel` seeds the second and yields to the first; `isInConversationModelChoice` is the line between them, and every "may the default win here?" question routes through it. It also names the three phases each public operation belongs to — initialize, reconcile, sync — and says which two operations deliberately sit outside them. Finally it records why the two surfaces are allowed to differ, and only here: Workbench chat may show a stand-in while a model is unpublished because the cost of being wrong is a repaint, while the Agents Window writes through to a backend and so must wait. Documentation only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Keep one record of how a chat came by its model Selection kept two. The controller recorded a reason on the conversation's intended model; the Agents Window kept a parallel `ChatModelSource` per conversation, updated on the same events, with its own change detection beside the controller's echo suppression. Two records of one fact, held in step by hand. The provider source is now derived from the reason. What has to survive that round trip is whether the model speaks for the conversation, and it does: every reason maps to a source that maps back to a reason on the same side of the choice/spillover line, so echo suppression and the `chat.defaultModel` rule are unchanged. A user's own pick returns as `Restored` rather than `User` once written and read back — both are the conversation's own, so no outcome differs; only the label is coarser. The test that pinned the old label now asserts the property the rule actually turns on. Deleting the field also removes the record-before-write and roll-back-on-throw dance at both provider writes, which existed only to keep the copy in step. Two further changes in the same spirit: `_applyModel` now takes the reason it is applying under. It records it before handing the model to the surface, because a surface that persists reads the reason during that call — the shape of a bug already fixed once on this branch. Sites that deliberately carry the current reason over, such as canonicalizing an identifier, now say so instead of relying on the absence of an assignment. `requiresCustomModels` leaves the seam. It was the last member phrased as one surface's inputs rather than as the question being asked; it is now an optional `isAwaitingSessionModels`, which the Agents Window omits because a provider snapshot is already the session's own pool. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Leave only shared work on the shared selection controller The controller had grown entry points that only Workbench chat ever called, so reading it meant deciding for each one whether the Agents Window relied on it. Five did not, and did not need to be there: - `beginSessionSwitch`, `endSessionSwitch` and `restorePerTypeModel` existed to latch a single boolean across a widget handshake — a decision made before the view model arrives and acted on after. That is the widget's business, and the latch now lives beside the handshake it belongs to. What the controller keeps is `beginConversationSwitch`, which both surfaces call for the part that is shared: dropping what spoke for the outgoing conversation. - `resolveDraftModel` and `reinitializeIfOutsidePool` were compositions of things the widget already knows — its own catalog, its configured default, its current model — expressed through the controller rather than directly. `revalidateForSessionType` stays despite also being Workbench-only: it reaches the selection reason and applies models, so moving it would mean widening the controller to let it back in, which is the opposite of the point. The public surface goes from 22 members to 17, and the per-type restore latch leaves the shared class entirely rather than being re-housed behind an accessor. The test that covered that latch goes with it; what it asserted is now the one-line expression that sets it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Reuse the per-type restore rule instead of restating it Moving the restore latch into the widget inlined its expression, leaving `shouldRestorePerTypeModelOnSessionSwitch` — which already stated the same rule, with the reasoning for it and its own tests — unused. The widget now calls it, so the rule has one statement and its tests cover live code again. Also drops two references to `beginSessionSwitch` left behind in the policy header and in `beginConversationSwitch`, which no longer resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Stop a reopened session being switched to the last model used elsewhere Reopening a finished session could silently move it to whatever model was picked most recently anywhere in the profile. A GPT session reopened after visiting an Opus one came back on Opus, and the write went through to the backend, so it stayed there. Two things combined to cause it. An agent-host session reports `modelId` as undefined until something says otherwise, and nothing did: the provider hydrates the selected agent from the default chat's persisted draft but never the model. Model selection then read "no model" as "this conversation has never chosen one", which is the state a remembered preference exists to seed. Both halves are fixed. `_hydrateModelFromDraft` mirrors the agent hydration already beside it, reading the model back from `ChatState.draft.model` and recording it as `Restored` — what it is, the conversation's own model read back from where the host kept it. Like its counterpart it is one-shot and guarded, so it cannot override a selection made in the meantime. Independently, a conversation that has already run is no longer *given* a model. Its own model may simply not have arrived yet, and a profile-wide preference is not an answer for it: showing one keeps the picker from being blank, but writing one changes what the conversation runs on rather than describing it. Only a conversation that has yet to run can be seeded. A pick the user makes is unaffected — that is an answer for this conversation, and still writes. The second half stands on its own: it closes the window before hydration completes, and covers providers that report no model at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Do not let the configured default take a model the user just picked Selection asked the controller whether `chat.defaultModel` could seed while a wanted model was still unpublished, but answered its own question first: with no model yet on the chat it passed `SessionRestore`, which reads as "nothing has claimed this conversation". That overrode what the controller already knew. So between a user picking a model and the provider echoing it back, a catalog refresh that dropped the pick let the configured default win — the case this series exists to prevent, reached through the one call that talked over the controller instead of asking it. It now passes what it actually knows, and `undefined` means "you decide", which is what the parameter was documented to mean. The controller then sees its own pending user choice and holds. The wrapper this lived in was a single line with one caller and is gone with it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Say it once, in plain English The comments had grown into essays. The controller was 31% comment with ten blocks of six lines or more, the adapter 28%, and `sessionModelProvenance.ts` was 56% — more comment than code. Much of it restated the line below, or explained the same rule two or three times in different words. Cut to one-liners wherever the code already says it, keeping length only where a comment records something the code cannot: why a reason is set before a value is handed over, why an echo has to be ignored, why one surface waits where the other shows a stand-in. The file header keeps the rule the file exists to enforce and drops the tour of the API around it. Also removed, all unused: the `source` discriminant on the remembered selection, which nothing read, and the `conversationKey` parameter to `_applySessionRestore`. Parenthesised a ternary whose `||` chain read as though it bound the whole expression, and corrected the `setModel` signature in the Copilot provider's README. Two tests asserted the wording of diagnostic log lines, which no user can observe and any rewording breaks. They now assert the behaviour they were reaching for. Two others were renamed to what they actually check: one claimed to be about enabling send while asserting empty picker state. No behaviour change. 254 lines shorter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 * Show a stand-in rather than nothing while a reopened chat's pool publishes Reopening a chat that had already run, while its agent host was still connecting, left the model picker blank and the composer refusing to send until the catalog settled. The desired-model probe falls back to the profile-wide preference when the chat has no model of its own yet, which is exactly the window before the provider hydrates one. An agent-host vendor deliberately reports an absent model as `pending` rather than `unavailable` while its catalog is in flight, so the probe came back pending, and the wait blanked the picker: nothing is shown while a selection is pending, and a pending selection also blocks send. The wait exists to keep a transient stand-in from being written through to the backend and changing what the conversation runs on. But a conversation that has already run is display-only, and `_pushModelToProvider` already withholds every write for those. So the wait was guarding a write that was never going to happen, and charging the picker for it. It now applies only where there is something to guard. A conversation that would be written to still waits; a display-only one falls through and shows the nearest thing its pool offers, which is what the sibling case immediately above it in the tests already expected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dadcb0d5-d0b7-4bd2-a092-75982f51c63a * Say where a model came from without saying "provenance" "Provenance" is a word most readers have to stop and translate, and it earned its place here only by being short. The thing it names already has a plainer name in the code it describes: `ChatModelSource`, and `IChat.modelSource`. So the file that translates between a provider's account of a model and the controller's is now `sessionModelSource.ts`, matching the type it converts, and the comments say "where the model came from" or "credited to the wrong source". Nothing else changes: same functions, same call sites, same behaviour. Left alone is `derives automation provenance from the provider run ledger`, which predates this work and is about something else. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dadcb0d5-d0b7-4bd2-a092-75982f51c63a * Cover the two behaviour changes that had no tests, and drop a dead helper Three loose ends from review. `findDefaultModel` no longer has a production caller. The controller asks the surface for its declared default and falls back to the first model itself, which is the same rule spelled out at the seam, so the helper is gone. Its behavioural callers in the tests were computing "what would a reset land on", so that composition now lives in the test file beside `computeAvailableModels`, which already does the same for the model pool. The two suites that only exercised the helper itself went with it: the rule they covered is asserted against the real controller, which is where it now lives. Two behaviour changes shipped in this series without tests, both of them forced by the rule that a conversation which has already run is never given a model — without them such a chat would have no model at all rather than the wrong one. `_hydrateModelFromDraft` now has the pair its counterpart already had: a resumed session picks its model back up from the persisted draft as `Restored`, and a live pick still wins over a later draft snapshot. `forkChat` is covered for starting the new chat on the source chat's own model rather than the session-level default it used to take, asserted through the host call and the new chat's input state. That last test does not assert the forked chat's `modelSource`, which does not come back as `Inherited`: the write reports no entry for the chat the catalog just created, and re-emitting the catalog does not seed it either. Left as-is rather than guessed at, since it is the same path `createSideChat` and `createNewChat` take and none of them assert it. Also corrects SESSIONS.md, which announced two invariants and listed three. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dadcb0d5-d0b7-4bd2-a092-75982f51c63a * Assert the inherited source a fork records, and stop the harness evicting it The fork test stopped short of asserting the thing the change is for: that the new chat records its model as `Inherited` rather than as its own. It was left out because the assertion failed, and the reason was not understood. The reason was the harness. `setupMultiChatSession` announced a session with a `SessionAdded` notification but never registered it with the mock host, so the `getSessions()` that follows started a refresh whose authoritative session list came back empty — and an empty authoritative list evicts the adapter the notification had just created. The catalog was materialised on that adapter; the inherited-model write then landed on it after eviction, on an instance nothing reads, which is why the chat surfaced with no source and why re-emitting the state did not seed it either. Registering the session before announcing it makes the two consistent, which is what a real host reports, and the assertion then holds: the forked chat carries the source chat's model with `ChatModelSource.Inherited`, so `chat.defaultModel` may still seed it. No other multi-chat test changes behaviour. Worth noting what this does not fix. `createNewChat`, `forkChat` and `createSideChat` each capture the adapter before awaiting the host and use it again afterwards, without rechecking that it is still the cached one. The harness reached that window deterministically; production would need an eviction mid-creation to do the same, but nothing prevents it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dadcb0d5-d0b7-4bd2-a092-75982f51c63a * Say "the chat's own" or "carried over", and say it once Model selection acted on a two-state question — is this model the chat's own? — through four `ChatModelSource` values, a lossy mapping in each direction, and a third spelling in the conformance harness. Collapse the enum to `Chosen` / `CarriedOver`. `User` and `Restored` always answered alike, as did `Inherited` and `Automatic`, and `sourceForReason` proved it: expanding a reason back into four values was only ever `isInConversationModelChoice(reason)` wearing a switch statement. It is now exactly that, so `sessionModelSource.ts` has nothing left to translate and goes away. Give the rule one home: `isChatOwnModel` says that an absent source counts as owned, and both directions read it rather than restating it. Shorten the SESSIONS.md section to what a reader needs, and drop "spillover" for plain English throughout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bcbe1455-6e2d-4d70-a125-dc342b293b5b * Do not let a stand-in take the model a chat is waiting for Three review findings. A chat that falls back to a stand-in writes it back as carried over. Rebinding the chat then read that stand-in as its answer and dropped the model it was waiting for, so the model was never reclaimed and `chat.defaultModel` was free to seed over it. The old guard only caught this when nothing had been bound in between: it asked whether the arriving model was the one on screen, and after a peer visit the screen belongs to the peer. Ask the bound conversation instead — it is still waiting, and a carried-over model is not an answer for it. Workbench cannot say where a draft model came from, so it keeps the on-screen test as a second way in. `createFixtureActiveSession` supplied a chat with only `resource`, which threw once selection started reading `status` and `modelSource` — all twelve prompt-options fixtures errored, failing the fixture job. `forkChat` fell back to the session's model for a peer whose own model this client does not know, which after a reload forks it onto a model it was never running. State none and let the host answer, as `createSideChat` does. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bcbe1455-6e2d-4d70-a125-dc342b293b5b --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 703445d9-b313-4faa-abf9-1fc939a01c65 Copilot-Session: dadcb0d5-d0b7-4bd2-a092-75982f51c63a Copilot-Session: bcbe1455-6e2d-4d70-a125-dc342b293b5b
Log the effective BYOK state and its environment and synchronized root-config inputs when publishing models and building session configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep BYOK enablement diagnostics available at trace level without adding routine info-level noise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ing (#331402) * Refactor hook scanning logic and improve folder picker decision handling * Enhance folder picker decision logic to honor filesystem casing and improve hook detection documentation
Document that the BYOK environment variable is an explicit override and that synchronized root configuration supplies the normal enablement value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hub.com/microsoft/vscode into agents/add-logs-tests-byok-models-issue
* Chat: navigate back to session list with mouse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Chat: consume mouse back navigation event Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Chat: capture mouse back navigation globally Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…models-issue agentHost: sync BYOK experiment enablement
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )