Fix misleading Chat model picker and Sign In affordances in untrusted (Restricted Mode) workspaces#322627
Draft
rwoll wants to merge 6 commits into
Draft
Fix misleading Chat model picker and Sign In affordances in untrusted (Restricted Mode) workspaces#322627rwoll wants to merge 6 commits into
rwoll wants to merge 6 commits into
Conversation
In an untrusted (Restricted) workspace the chat model providers are disabled, so no language models register. The model picker previously fell back to a misleading lone "Auto" entry, implying Auto was the only available model and leaving users unsure whether they were missing models. The picker now detects this state (workspace trust initialized, workspace untrusted, no live models) and: - shows a neutral "Pick Model" placeholder on the button (no fake "Auto"), - replaces the dropdown contents with a disabled "Models Unavailable in Restricted Mode" header and an enabled "Trust Workspace..." action that calls requestWorkspaceTrust(), mirroring the prompt shown when sending a message, - explains the state via the button hover and an aria label. Detection uses the live model registry (not the cache-backed display list, which can hold stale models from a previous trusted session) and is gated on workspaceTrustInitialized to avoid briefly rendering a trusted workspace as restricted at startup. On trust granted, onDidChangeLanguageModels / onDidChangeTrust refresh the picker. Also hides the title-bar and accounts "Sign In" affordances in untrusted workspaces, where signing in is not the blocker (trust is). The deeper "chat appears signed-out in untrusted workspaces" problem is tracked separately in microsoft#322626. Fixes microsoft#322564. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restricted Mode picker (review feedback): - Render the explanatory "Models Unavailable in Restricted Mode" line as a non-interactive Header instead of a fake disabled action (which was exposed as a `menuitemradio`). - Gate the "Trust Workspace..." action's `enabled` state on a provided `onRequestTrust` callback so it isn't presented as actionable when it can't do anything. Post-Trust activation (Fixes microsoft#322626): - chatSetupRunner: when Trust is granted from an untrusted workspace, wait for the chat extension to finish activating before deciding whether to show the sign-in dialog. Previously the decision used a stale, not-yet-resolved entitlement and briefly showed "Sign in to use GitHub Copilot" to users who are already signed in. Detection relies only on the extension lifecycle (`IExtensionService` activation status) and never touches the auth session, so no authentication happens in untrusted workspaces. - Model picker: after Trust, show a transient "Activating..." state while models load (only when there is nothing else to display) instead of a misleading "Auto". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After Trust, the workspace is trusted but the entitlement reads "signed out" until the chat extension finishes activating, so the title-bar/accounts Sign In button briefly appeared while the model picker showed "Activating...". Add a `chatSetupActivating` context key (true while the chat extension is registered but not yet activated) and gate the Sign In affordances on it. Detection uses the extension lifecycle only and never touches the auth session. Guards the inverse case: once activation completes (`activationTimes` set) or fails (`runtimeErrors`), the key is false, so a signed-out user with an activated extension still sees the Sign In button. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Show the title-bar/accounts Sign In affordance only when the default account is `unavailable` (resolved + genuinely signed out), replacing the workspace-trust mask and the `chatSetupActivating` context key. The default-account state resolves from workbench auth sessions independent of workspace trust and the chat extension, so this correctly hides Sign In for an already-signed-in user even in an untrusted workspace, shows it for a signed-out user (including untrusted), and avoids the resolution-time flash — without probing auth itself (the workbench resolves it regardless). Removes the now-unused activation-tracking and `chatSetupActivating` context key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…raw registry The Restricted Mode (untrusted workspace) state was detected by counting the raw global language model registry (`getLanguageModelIds()`). In the agents/untrusted scenario the Agent Host registers session-scheme-scoped models into that same global registry; those are not usable by the panel/inline picker (its `delegate.getModels()` is location/session-filtered and stays empty, so the dropdown shows a synthetic "Auto"), yet they made the raw count non-zero and flipped `isRestrictedMode()` to false — showing a misleading "Auto" instead of the Restricted Mode state. Replace the detection with a pure `isRestrictedModeState` helper that counts a model as usable only when it is both offered by this picker (`delegate.getModels()`) AND live in the registry. This ignores both phantom sources that masked the state: stale cross-window machine cache entries, and models registered for other surfaces (e.g. agent-host session-scoped models). The fix applies uniformly to every picker built on the shared `ModelPickerWidget` — single chat and inline chat (via `chatInputPart`) and the sessions/agents chat. Also make the sessions picker surface the Restricted Mode state instead of hiding: `_shouldShowPicker` keeps the picker visible when restricted, and it re-evaluates on workspace-trust changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines
+60
to
64
| export enum DefaultAccountStatus { | ||
| Uninitialized = 'uninitialized', | ||
| Unavailable = 'unavailable', | ||
| Available = 'available', | ||
| } |
Comment on lines
+497
to
+512
| items.push({ | ||
| item: { | ||
| id: 'restrictedModeTrust', | ||
| enabled: !!onRequestTrust, | ||
| checked: false, | ||
| class: undefined, | ||
| tooltip: localize('chat.modelPicker.restrictedMode.trustTooltip', "Trust the workspace to enable AI models."), | ||
| label: localize('chat.modelPicker.restrictedMode.trust', "Trust Workspace..."), | ||
| run: () => onRequestTrust?.() | ||
| }, | ||
| kind: ActionListItemKind.Action, | ||
| label: localize('chat.modelPicker.restrictedMode.trust', "Trust Workspace..."), | ||
| group: { title: '', icon: ThemeIcon.fromId(Codicon.workspaceTrusted.id) }, | ||
| disabled: !onRequestTrust, | ||
| hideIcon: false, | ||
| }); |
…is confirmed The title-bar and accounts-menu Sign In affordances were gated on `defaultAccountStatus === Unavailable`, which is only true once the default account positively resolves to signed-out. During the `Uninitialized` window (and any case where the state has not resolved) the button was suppressed even though the user is signed out, so it appeared to be missing. Gate instead on `defaultAccountStatus !== Available`: the default-account state is used purely as a "hide only when signed in" guard, layered on the original `Entitlement.signedOut` trigger. The button is now shown while signed out or before the account state resolves (matching the original behavior, including untrusted workspaces), and hidden only when a default GitHub account is positively present — including untrusted workspaces and while the extension is still activating. No auth is probed by this code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #322564
Fixes #322626
tl;dr: new look has "Pick a Model" + info about Workspace Trust, while before this just said "Auto" and it wasn't clear the user needed to trust the workspace. Adopts the behavior of the chat+send button: on first send, if untrusted, prompt for trust.
Fixes the confusing Chat experience in untrusted (Restricted Mode) workspaces, where the model providers and entitlement aren't available until the workspace is trusted and the extension activates.
Model picker reflects Restricted Mode
Previously the picker fell back to a lone "Auto" entry, so users couldn't tell whether they were missing models.
requestWorkspaceTrust()), mirroring the prompt shown when sending a message.chatInputPart) and the sessions/agents chat — which all shareModelPickerWidget.Robust detection (
isRestrictedModeState)A workspace is treated as restricted only when it is untrusted and the picker has no usable model. A model counts as usable only when it is both offered by this picker (
delegate.getModels(), already filtered to the picker's location/session) and live in the language model registry. This deliberately ignores two phantom sources that previously masked the state and made the picker show "Auto":Detection is gated on
workspaceTrustInitializedto avoid a restricted flash at startup. The sessions picker also stays visible (instead of hiding itself) when restricted so it can surface the Trust action, and re-evaluates on workspace-trust changes.No misleading "signed out" state during/after Trust
After granting Trust, the entitlement reads "signed out" until the extension finishes activating, which briefly surfaced a Sign In button and a "Sign in to use GitHub Copilot" modal to already-signed-in users.
defaultAccountStatus !== available). The button is shown whenever the user is not signed in — including before the account state resolves — and hidden only when a default GitHub account is positively present. This resolves from auth sessions independent of workspace trust and the extension, so it correctly hides for a signed-in user even in untrusted workspaces and while the extension is still activating, and still shows for a genuinely signed-out user (including untrusted). No auth is probed by this code.chatSetupRunner): after Trust from an untrusted workspace, waits for the chat extension to finish activating (viaIExtensionServicestatus only) before deciding whether to show the sign-in dialog, so existing Pro/Free users skip it.Testing
isRestrictedModeStateinchatModelSelectionLogic.test.ts(untrusted vs. trusted, pre-trust-init, live BYOK model, and the stale-cache / agent-host masking cases) plus the restricted dropdown inchatModelPicker.test.ts(header/Trust action, precedence over cache, Trust callback, disabled-without-callback).npm run typecheck-client, eslint,npm run valid-layers-check, and the picker suites (145 selection-logic + 70 builder + 76 picker) all pass; hygiene hook.ModelPickerWidget, so single, inline, and agents chat are covered by the same fix.