Skip to content

Omni-chat with multi-session routing - #326698

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

Omni-chat with multi-session routing#326698
meganrogge wants to merge 41 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 omni chat input that hosts the real compact ChatWidget, including dictation, Voice Mode, multiline editing, and the input glow. The surface has dedicated drag and close controls, grows with typed input, and expands to show routing choices below the prompt.

Toggle it with Toggle Floating Chat Input Window (workbench.action.chat.toggleInputWindow).

How session finding works

Submitting does not run the request in the omni window's scratch session. Instead, ChatSessionRoutingController finds the most relevant existing agent session:

  1. It loads non-local agent sessions and excludes the omni window's own scratch session.
  2. It cheaply pre-ranks candidates from in-memory metadata such as title, description, status, and recent activity.
  3. It enriches the five most plausible candidates with conversation content: the first request, latest request, and latest textual response.
  4. ISessionRouter scores the enriched candidates with the utility language model, falling back to the token-overlap heuristic when the model is unavailable.
  5. A result above the confidence threshold is preselected. When the last-used session is close to the top score, it is preferred to keep related follow-up turns together. If nothing is confident enough, Start a new session is preselected.

Visible, correctable routing

Before dispatch, the omni bar renders a ranked Send To panel below the input. It shows the selected destination, close alternatives, confidence percentages and meters, and a 10-second countdown. Routing is never silent:

  • Press Option/Alt to move through destinations.
  • Click a row to send there immediately.
  • Command/Ctrl-click multiple rows to fan the request out to several sessions.
  • Press Enter to send the current selection.
  • Press Escape to cancel.
  • Continue typing to cancel the pending send and edit the prompt.

After dispatch, the surface briefly confirms the destination and links to a single routed session when applicable. The last routed session is remembered at workspace scope and biases later related requests.

Voice routing and backend follow-up

The client side of Voice Mode routing is implemented here:

  • A single resolved route pins IVoiceSessionController to the actual session resource before the request is sent, so subsequent voice input and response narration follow that session rather than the omni window's scratch session.
  • Session context marks the active target with omni_route: "existing_session" or omni_route: "new_session".
  • Fan-out clears the sticky voice target instead of arbitrarily choosing one of several destinations.
  • Disconnecting clears both the target and its omni routing metadata.

The voice backend must add support for the new omni_route field. For existing_session, it should acknowledge the named targeted session and read/narrate that session's response. For new_session, it should say "I'm creating a new session" before continuing narration from the newly created target. Until that backend support lands, routing and target pinning work, but this spoken transition is not produced.

Window and input behavior

  • The visible hamburger region moves the frameless window; Send and Close remain separate trailing controls.
  • The compact editor is vertically centered at one line and the auxiliary window grows with multiline input up to the compact editor limit.
  • The hidden response list is collapsed so the input-only surface has no black reserved area.
  • The outer window shows a focus border whenever focus is within the omni bar.
  • Ranked routing rows trigger native auxiliary-window bounds updates, so the full panel is shown without clipped content or empty reserved space.
  • The model picker can temporarily expand the window and restore its fitted height afterward.

Implementation

  • chat/common/sessionRouter.ts defines the pure routing contract, model prompt/parser, and heuristic fallback.
  • chat/browser/sessionRouter/ performs candidate collection, bounded transcript enrichment, ranking, selection, countdown, dispatch, and confirmation.
  • chat/browser/chatInputWindow/ owns the auxiliary surface, compact widget, drag/close chrome, and content-driven sizing.
  • chat/browser/voiceClient/voiceSessionController.ts receives the routed target and adds the omni route decision to voice session context.
  • The shared auxiliary-window bounds API keeps browser-layer callers independent of Electron-only native services.

Validation

  • Targeted ESLint for changed TypeScript files
  • npm run valid-layers-check --silent
  • Pre-commit hygiene

🤖 Generated with GitHub Copilot CLI

Focus-aware voice dispatch

When the omni auxiliary window owns focus, finalized Voice Mode text is submitted through its ChatSessionRoutingController rather than the panel command bridge or most-recent-session fallback. The bridge is command-based to avoid a service-construction cycle while the omni ChatWidget initializes; panel-focused Voice Mode behavior remains unchanged.

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 16 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
…indow

# Conflicts:
#	src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts
…routing

- Added a toggle action for the floating chat input window in the sessions header when `chat.omni.enabled` is active.
- Registered the chat input window service and integrated it with the existing chat functionality.
- Enhanced session routing to utilize enriched session data (description, first/last requests, and last response) for improved matching.
- Updated accessibility help to describe the floating chat input window when enabled.
- Implemented clipping for long content fields in session routing to prevent overflow in prompts.
- Added tests to verify the new functionality and ensure proper behavior of the routing logic with enriched content.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The content surface stacked a second bordered/rounded box around the
chat input's own rounded container, producing mismatched concentric
corners. Make the surface an invisible transparent host with a symmetric
padding ring (which also serves as the drag region), so only the input
box's own consistent rounded corners show.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…uick Chat width

- Move close (X) action inside the input box via MenuId.ChatExecute
- Focus input so the text cursor renders on open
- Hide the attach-context (+) button in the omni-chat window
- Remove dead ChatInputWindowSide menu and import
- Clamp model-detail hover to the viewport with scroll
- Widen default window to Quick Chat width (min(innerWidth * 0.62, 600))

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The native edit-context focus tracker consulted the globally-active
document via getActiveElement() instead of the DOM node's own document.
In the frameless auxiliary chat-input window the edit context could hold
DOM focus while the tracker latched _isFocused=false, hiding the caret.
Track focus against the DOM node's owner document instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the visible input-box chrome the drag surface for the frameless
floating chat input window instead of relying on the thin transparent
padding ring. The .interactive-session wrapper is now marked
-webkit-app-region: drag, and the interactive interior (editor,
toolbars, attachments) is carved back out as no-drag so those controls
stay clickable/typable. Exposes ChatInputPart.interactiveInputElements
so the window host can do this without querying the DOM.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the tracker's own focus() so focus state is refreshed synchronously,
avoiding reliance on the DOM focus event which is unreliable when the
host window is not active (the frameless aux chat-input window case).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adopt the same approach the command center uses in the title bar: a
dedicated, full-size .chat-input-window-drag-region layer sits behind the
input and is marked -webkit-app-region: drag in CSS, while the interactive
controls (editor, toolbars, attachments) render on top and are marked
no-drag. This gives a large, reliable drag surface (the input-box chrome)
instead of relying on a thin transparent ring or fragile app-region
inheritance.

Also center the window on the window that invoked it (the active window)
rather than always the main window.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…indow

# Conflicts:
#	src/vs/workbench/contrib/chat/browser/speechToText/chatSpeechToTextService.ts
#	src/vs/workbench/contrib/chat/browser/voiceClient/micCaptureService.ts
#	src/vs/workbench/contrib/chat/test/browser/accessibility/chatAccessibilityHelp.test.ts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Screenshot Changes

Base: ae4238a3 Current: 9c6df30e

Changed (2)

chat/input/chatInput/VoiceModeConnecting/Dark
Before After
before after
chat/input/chatInput/VoiceModeConnecting/Light
Before After
before after

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
@meganrogge meganrogge changed the title Floating chat-input window with multi-session routing Omni-chat with multi-session routing Jul 31, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b11adb7d-72c1-4e64-ba1e-f798dd9bf490

This comment was marked as spam.

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