Skip to content

Fix misleading Chat model picker and Sign In affordances in untrusted (Restricted Mode) workspaces#322627

Draft
rwoll wants to merge 6 commits into
microsoft:mainfrom
rwoll:rwoll/chat-model-picker-restricted-mode
Draft

Fix misleading Chat model picker and Sign In affordances in untrusted (Restricted Mode) workspaces#322627
rwoll wants to merge 6 commits into
microsoft:mainfrom
rwoll:rwoll/chat-model-picker-restricted-mode

Conversation

@rwoll

@rwoll rwoll commented Jun 23, 2026

Copy link
Copy Markdown
Member

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.

Screenshot 2026-06-23 at 6 43 39 PM Screenshot 2026-06-23 at 6 43 54 PM

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.

  • Button shows a neutral "Pick Model" placeholder (no fake "Auto").
  • Dropdown shows a non-interactive "Models Unavailable in Restricted Mode" header + an enabled "Trust Workspace…" action (requestWorkspaceTrust()), mirroring the prompt shown when sending a message.
  • Applies across all model pickers — single chat and inline chat (via chatInputPart) and the sessions/agents chat — which all share ModelPickerWidget.

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":

  • stale cross-window machine cache entries (present in the delegate list, but not live), and
  • models registered for other surfaces — e.g. agent-host session-scoped models that the Agent Host registers into the global registry but that this picker doesn't offer (this is what made the agents/sessions picker show "Auto" in an untrusted workspace).

Detection is gated on workspaceTrustInitialized to 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.

  • Title-bar/accounts Sign In keeps its existing signed-out trigger and adds the workbench default-account state as a hide-when-signed-in guard (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.
  • Setup modal (chatSetupRunner): after Trust from an untrusted workspace, waits for the chat extension to finish activating (via IExtensionService status only) before deciding whether to show the sign-in dialog, so existing Pro/Free users skip it.
  • Model picker shows a transient "Activating…" state after Trust while models load (only when there's nothing else to display).

Testing

  • Unit tests for isRestrictedModeState in chatModelSelectionLogic.test.ts (untrusted vs. trusted, pre-trust-init, live BYOK model, and the stale-cache / agent-host masking cases) plus the restricted dropdown in chatModelPicker.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.
  • Restricted-mode picker behavior confirmed in a dev build: an untrusted workspace now shows "Pick Model" (+ Trust action) instead of "Auto". All pickers share ModelPickerWidget, so single, inline, and agents chat are covered by the same fix.
  • Sign In button behavior confirmed in a dev build: shown when signed out, hidden when signed in (including untrusted workspaces).
  • ⚠️ The post-Trust setup-modal gating still wants runtime verification with a signed-in Copilot account (draft).

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>
Copilot AI review requested due to automatic review settings June 23, 2026 22:10
@rwoll rwoll marked this pull request as ready for review June 23, 2026 22:14
@rwoll rwoll enabled auto-merge (squash) June 23, 2026 22:14

This comment was marked as outdated.

@rwoll rwoll disabled auto-merge June 23, 2026 22:44
@rwoll rwoll marked this pull request as draft June 23, 2026 22:44
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>
@rwoll rwoll changed the title Show Restricted Mode state in chat model picker for untrusted workspaces Improve Chat model picker + setup UX in untrusted (Restricted Mode) workspaces Jun 23, 2026
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>
@rwoll rwoll changed the title Improve Chat model picker + setup UX in untrusted (Restricted Mode) workspaces Fix misleading Chat model picker and Sign In affordances in untrusted (Restricted Mode) workspaces Jun 23, 2026
@rwoll rwoll closed this Jun 24, 2026
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 9/9 changed files
  • Comments generated: 2

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants