Skip to content

Floating chat-input window with multi-session routing#326698

Draft
meganrogge wants to merge 13 commits into
mainfrom
megrogge/chat-input-window
Draft

Floating chat-input window with multi-session routing#326698
meganrogge wants to merge 13 commits into
mainfrom
megrogge/chat-input-window

Conversation

@meganrogge

@meganrogge meganrogge commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes https://github.com/microsoft/vscode-internalbacklog/issues/8461

What

Adds a floating, frameless, always-on-top chat-input window that hosts the real chat input — dictation, voice mode, and the input glow — via a compact, input-only ChatWidget. When you submit, the request is scored against your existing sessions and routed to the best match, but never silently: an advisory badge shows the chosen target and a short countdown, then auto-sends unless you intervene.

Toggle with the command "Toggle Floating Chat Input Window" (workbench.action.chat.toggleInputWindow).

Routing — advisory badge with auto-send

On submit, the utterance is scored against the current session list (IAgentSessionsService) via ISessionRouter, and the window resolves a single pending target:

  • A confident match (top score ≥ ROUTE_CONFIDENCE_THRESHOLD, biased toward the last-used session when it's within ROUTE_AMBIGUITY_MARGIN of the top) → that session.
  • No confident match → a brand-new session.

It then shows an inline badge in the window — Sending to «target» · N% match · sending in 3…2…1 — and dispatches when the countdown (ROUTE_AUTOSEND_DELAY_MS) elapses. Before it fires you can:

  • Change — pick another target from all scored candidates (each showing N% match + rationale), preselected to the current target, plus "New session"; retargets and restarts the countdown.
  • Cancel — abort the send and keep the input to edit/retry.
  • Keep typing — any edit to the input cancels the auto-send.

The last routed session is remembered (workspace storage) so repeated turns bias toward the same session. This keeps a hands-free/voice flow fast while ensuring a request is never dispatched without a visible, correctable step.

Design

  • ISessionRouter (chat/common/sessionRouter.ts) keeps prompt/parse/heuristic logic pure so the scoring backend (renderer language model now — the small copilot-utility-small utility model — a CAPI utility completion or a local model later) can be swapped without changing the contract. It degrades to a zero-dependency token-overlap heuristic when no model is available.
  • ChatInputWindowService (chat/browser/chatInputWindow/) mirrors the proven AgentsVoiceWindowService aux-window shell and hosts a ChatWidget with renderStyle:'compact', renderInputOnTop, filter:() => false (input-only), and enableImplicitContext:false (no attachment row). The glow applies for free since the aux container tracks the monaco-workbench class. The badge, countdown timers, and their listeners are window-scoped disposables cleared on cancel / retarget / resend / window close.
  • The window's own session is a scratch input buffer only; it is excluded from routing candidates so it can never route to itself.

Files

  • chat/common/sessionRouter.ts, chat/browser/sessionRouter/sessionRouterService.ts (+ unit test)
  • chat/common/chatInputWindow.ts, chat/browser/chatInputWindow/{chatInputWindowService,chatInputWindow.contribution}.ts, chat/browser/chatInputWindow/media/chatInputWindow.css
  • Registrations in chat.shared.contribution.ts and workbench.common.main.ts

Notes / follow-ups

  • Draft: needs in-app testing of the aux window (glow, dictation, voice mode, and the badge countdown/interrupts) on a supported platform.
  • The "Change" quick-pick currently attaches to the workbench, so it may surface in the main window rather than the aux window; the badge itself is inline in the aux window.
  • Thresholds (ROUTE_CONFIDENCE_THRESHOLD, ROUTE_AMBIGUITY_MARGIN) and the auto-send delay (ROUTE_AUTOSEND_DELAY_MS) are constants that could be promoted to settings / tuned with telemetry.

🤖 Generated with GitHub Copilot CLI

Introduces a frameless, always-on-top auxiliary window that hosts the real
chat input (dictation, voice mode, and the input glow) via a compact,
input-only ChatWidget. Submissions are scored against the current session
list by a new ISessionRouter and dispatched with three outcomes:

- low confidence -> start a brand-new session
- several comparable high matches -> ask the user which session
- a single clear match -> dispatch straight to it

ISessionRouter keeps prompt/parse/heuristic logic pure so the scoring
backend (renderer language model now; CAPI utility or a local model later)
can be swapped without changing the contract; it degrades to a token-overlap
heuristic when no model is available. Toggle via the command
"Toggle Floating Chat Input Window".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aba188ca-29fb-4099-b0f9-e460fcb20613
Copilot AI review requested due to automatic review settings July 20, 2026 18:10

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.

Pull request overview

Adds a floating chat-input window that routes requests to matching agent sessions.

Changes:

  • Adds model-backed and heuristic session routing.
  • Adds the floating ChatWidget, disambiguation picker, and fallback routing.
  • Registers the services, command, contribution, and helper tests.
Show a summary per file
File Description
src/vs/workbench/workbench.common.main.ts Loads the floating-window contribution.
src/vs/workbench/contrib/chat/test/common/sessionRouter.test.ts Tests routing helpers.
src/vs/workbench/contrib/chat/common/sessionRouter.ts Defines routing contracts and pure helpers.
src/vs/workbench/contrib/chat/common/chatInputWindow.ts Defines the window service contract.
src/vs/workbench/contrib/chat/browser/sessionRouter/sessionRouterService.ts Implements model-backed routing.
src/vs/workbench/contrib/chat/browser/chatInputWindow/chatInputWindowService.ts Implements the window and dispatch workflow.
src/vs/workbench/contrib/chat/browser/chatInputWindow/chatInputWindow.contribution.ts Registers the toggle command.
src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts Registers the router service.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 16
  • Review effort level: Medium

Comment thread src/vs/workbench/contrib/chat/browser/chatInputWindow/chatInputWindowService.ts Outdated
Comment thread src/vs/workbench/contrib/chat/browser/chatInputWindow/chatInputWindowService.ts Outdated
Comment thread src/vs/workbench/contrib/chat/common/sessionRouter.ts Outdated
Comment thread src/vs/workbench/contrib/chat/browser/chatInputWindow/chatInputWindowService.ts Outdated
@meganrogge meganrogge self-assigned this Jul 20, 2026
@meganrogge meganrogge added this to the 1.131.0 milestone Jul 20, 2026
meganrogge and others added 7 commits July 20, 2026 14:32
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>
- Fix hygiene failure: replace non-ASCII box-drawing separator comment in
  sessionRouter.ts with ASCII; move $(add) icon outside the localized string.
- Draggable frameless window: add a -webkit-app-region drag handle strip.
- Localize the window document title with a placeholder.
- Reapply themed colors on onDidColorThemeChange.
- Dedicated accessibility help (routing + how to close) via inChatInputWindow
  context key, outranking the generic Quick Chat help.
- Forward and clear explicit attachments through the routing submit contract.
- Dedicated scoped ChatInputWindowSide menu + Close action (no longer closes
  the unrelated Quick Chat surface).
- Only clear the input if the editor still holds the submitted text.
- Window-scoped submission CancellationTokenSource, canceled on close.
- Await agent-sessions model.resolve before snapshotting candidates.
- Retain at most one session reference per resource (ResourceMap dedupe).
- Calibrate the offline heuristic against candidate metadata + threshold test.
- Coalesce concurrent openWindow calls; guard unload cleanup by window identity.
- Use dom.addDisposableListener for the beforeunload listener.
- Rethrow CancellationError from the router instead of degrading to heuristic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8904e5b5-6e65-4b9c-8ec5-ac060df81978
The session router scores candidate sessions as a background classification
task. Pin it to the small utility model (vendor: copilot, id:
copilot-utility-small) like other internal utility features
(chatGoalSummaryService, chatToolRiskAssessmentService, etc.) instead of
grabbing the first available Copilot model, which could be an expensive premium
model and is ordering-dependent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
…uting

Routing is no longer silent. After scoring the utterance against existing
sessions, the window resolves a single pending target (top match above the
confidence threshold, biased toward the last-used session; otherwise a new
session) and shows an inline badge naming that target with a short countdown.
When the countdown elapses the request auto-sends. The user can redirect it
("Change"), abort it ("Cancel"), or just keep typing — any edit cancels the
auto-send — so a request is never dispatched without a visible, correctable
step.

- Add ROUTE_AUTOSEND_DELAY_MS countdown + pending-send badge in the aux window.
- Remember the last routed session (workspace storage) and pre-select it.
- Generalize the target picker to list all scored candidates with a preselected
  entry, reused by the badge's "Change" action.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
@meganrogge

Copy link
Copy Markdown
Collaborator Author

Reworked routing from silent auto-dispatch to an advisory badge with auto-send, based on a design rethink:

  • The old flow silently sent each utterance to the top-scored session (or disambiguated only when ambiguous). Silent misroutes to an autonomous agent are high-cost and the LLM has thin signal on short utterances ("run the tests again").
  • New flow: after scoring, the window resolves a single pending target (top match ≥ threshold, biased toward the last-used session; otherwise a new session) and shows an inline badge in the aux window — Sending to «target» · N% match · sending in 3…2…1.
  • When the countdown elapses it auto-sends. The user can Change (picker of all scored candidates, preselected to the current target), Cancel (abort + keep the input to edit), or just keep typing (any edit cancels the auto-send). Routing is never silent, but the hands-free flow stays fast.
  • Remembers the last routed session (workspace storage) to bias the next suggestion.

Known follow-up: the "Change" quick-pick still opens on the main window rather than the aux window (per-window quick input is a larger change); the badge itself is inline in the aux window.

meganrogge and others added 5 commits July 22, 2026 16:37
… wire Quick Chat

Extracts the advisory-badge session routing from ChatInputWindowService into a
reusable ChatSessionRoutingController (shared CSS with generic class names) and
wires Quick Chat to it behind a new `chat.omni.enabled` setting.

- New `chat.omni.enabled` (experimental, default off) gates the omni experience
  on omni surfaces such as Quick Chat.
- No-match case no longer delays: it creates and sends to a new chat immediately
  and the badge links to that session (via IChatWidgetService.openSession) as
  soon as it exists.
- Confident-match case keeps the countdown + Change/Cancel/type-to-cancel flow.
- The floating input window now delegates to the shared controller.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
When the router auto-sends to a matched session, show a brief inline
confirmation badge ("Sent to «session»") with an Open link, so omni
surfaces that don't render the response inline (Quick Chat, aux input
window) still confirm where the request went. Auto-dismisses after 4s.
Guarded on the current submission so a newer submit isn't overwritten.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
Give more time to redirect or cancel before the matched send fires.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
The disambiguation picker renders into the focused aux window's
container; since that frameless window is only tall enough for the
input, the options were clipped. Resize the window to fit the rows
while the picker is open (via an onPickerVisibility host hook on the
shared routing controller) and restore the height on close.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ece96f2d-c878-4a57-97c5-884542b8581e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants