Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4b5a5f8
Add floating chat-input window with multi-session routing
meganrogge Jul 20, 2026
59f8a3a
Potential fix for pull request finding
meganrogge Jul 20, 2026
0e6fca8
Potential fix for pull request finding
meganrogge Jul 20, 2026
6dfd7c2
Address review feedback on floating chat-input window
meganrogge Jul 20, 2026
43ed32c
Merge branch 'main' into megrogge/chat-input-window
meganrogge Jul 21, 2026
9c6646a
Merge branch 'main' into megrogge/chat-input-window
meganrogge Jul 21, 2026
f79287b
Pin session router to copilot-utility-small model
meganrogge Jul 22, 2026
062d4f6
Chat input window: advisory badge with auto-send instead of silent ro…
meganrogge Jul 22, 2026
5a14efb
Omni routing: extract shared badge controller, add chat.omni.enabled,…
meganrogge Jul 22, 2026
fd40aea
Add "Sent to …" confirmation badge after a matched omni send
meganrogge Jul 22, 2026
152f58c
Lengthen routing auto-send countdown to 8s
meganrogge Jul 22, 2026
0e5962d
Set routing auto-send countdown to 10s
meganrogge Jul 22, 2026
e05b333
Grow aux input window to fit the routing picker
meganrogge Jul 22, 2026
b7f49ae
Merge remote-tracking branch 'origin/main' into megrogge/chat-input-w…
meganrogge Jul 30, 2026
c360601
feat(chat): introduce floating chat input window and enhance session …
meganrogge Jul 30, 2026
5c23295
fix(chat): polish floating input window
meganrogge Jul 30, 2026
798a0ef
fix(chat): single rounded box for floating input window
meganrogge Jul 30, 2026
563b1fa
Polish omni-chat: inside close button, cursor focus, remove attach, Q…
meganrogge Jul 30, 2026
049850f
improvements
meganrogge Jul 30, 2026
1d6e124
improvement
meganrogge Jul 30, 2026
12799cd
Fix cursor not showing in omni-chat floating input
meganrogge Jul 30, 2026
aa88378
Make the omni-chat input window draggable
meganrogge Jul 30, 2026
b2979ee
Refresh focus synchronously in FocusTracker regression test
meganrogge Jul 30, 2026
f8406d1
Make omni-chat input window draggable via command-center pattern
meganrogge Jul 30, 2026
4421e8a
Merge remote-tracking branch 'origin/main' into megrogge/chat-input-w…
meganrogge Jul 31, 2026
3c7a990
Polish omni chat routing surface
meganrogge Jul 31, 2026
90ff591
Fix omni chat layout sizing
meganrogge Jul 31, 2026
cd96f10
Grow omni chat input with content
meganrogge Jul 31, 2026
ac75ea4
Merge branch 'main' into megrogge/chat-input-window
meganrogge Jul 31, 2026
1431c53
Fix opening the omni chat window
meganrogge Jul 31, 2026
1b77224
Merge remote-tracking branch 'origin/megrogge/chat-input-window' into…
meganrogge Jul 31, 2026
fb05a1c
Route omni voice to selected sessions
meganrogge Jul 31, 2026
427cba4
Fix opening the omni chat window
meganrogge Jul 31, 2026
4deb033
Route focused omni voice without service cycles
meganrogge Jul 31, 2026
b8dab56
Restore omni chat opening lifecycle
meganrogge Jul 31, 2026
093e9fa
Add omni chat opening diagnostics
meganrogge Jul 31, 2026
67b7216
Keep omni focus ring within bounds
meganrogge Jul 31, 2026
d4646d4
Stabilize omni chat opening layout
meganrogge Jul 31, 2026
c47ce4f
Center omni in the invoking window
meganrogge Jul 31, 2026
6c62370
Filter low-confidence omni routes
meganrogge Jul 31, 2026
7850282
Merge branch 'main' into megrogge/chat-input-window
meganrogge Jul 31, 2026
0fdabdd
fix check
meganrogge Jul 31, 2026
7ad075f
Merge remote-tracking branch 'origin/megrogge/chat-input-window' into…
meganrogge Jul 31, 2026
46a16fd
push changes
meganrogge Jul 31, 2026
8c4f3c5
Harden omni routing lifecycle
meganrogge Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,9 @@
"--collapse-from-width",
"--slide-from-x",
"--slide-from-y",
"--omni-icon-column",
"--omni-rail",
"--omni-row-gap",
"--vg-w1",
"--vg-h1",
"--vg-w2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { addDisposableListener, getActiveElement, getShadowRoot } from '../../../../../base/browser/dom.js';
import { addDisposableListener, getShadowRoot } from '../../../../../base/browser/dom.js';
import { IDisposable, Disposable } from '../../../../../base/common/lifecycle.js';
import { ILogService } from '../../../../../platform/log/common/log.js';

Expand Down Expand Up @@ -67,7 +67,7 @@ export class FocusTracker extends Disposable {

public refreshFocusState(): void {
const shadowRoot = getShadowRoot(this._domNode);
const activeElement = shadowRoot ? shadowRoot.activeElement : getActiveElement();
const activeElement = shadowRoot ? shadowRoot.activeElement : this._domNode.ownerDocument.activeElement;
const focused = this._domNode === activeElement;
this._handleFocusedChanged(focused);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import assert from 'assert';
import { toDisposable } from '../../../../base/common/lifecycle.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
import { NullLogService } from '../../../../platform/log/common/log.js';
import { FocusTracker } from '../../../browser/controller/editContext/native/nativeEditContextUtils.js';

suite('NativeEditContextUtils', () => {

const disposables = ensureNoDisposablesAreLeakedInTestSuite();

test('tracks focus in the DOM node owner document', () => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
disposables.add(toDisposable(() => iframe.remove()));

const target = iframe.contentDocument!.createElement('div');
target.tabIndex = 0;
iframe.contentDocument!.body.appendChild(target);

let focused = false;
const tracker = disposables.add(new FocusTracker(new NullLogService(), target, value => focused = value));
// Use the tracker's own `focus()` so the focus state is refreshed
// synchronously. Relying on the DOM `focus` event is unreliable when the
// host window is not the active window (as with the frameless auxiliary
// chat-input window), which is exactly the case this fix targets.
tracker.focus();

assert.deepStrictEqual({
activeElement: iframe.contentDocument!.activeElement === target,
focused,
trackerFocused: tracker.isFocused,
}, {
activeElement: true,
focused: true,
trackerFocused: true,
});
});
});
1 change: 1 addition & 0 deletions src/vs/platform/accessibility/browser/accessibleView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const enum AccessibleViewProviderId {
InlineChat = 'inlineChat',
AgentChat = 'agentChat',
QuickChat = 'quickChat',
ChatInputWindow = 'chatInputWindow',
InlineCompletions = 'inlineCompletions',
KeybindingsEditor = 'keybindingsEditor',
Notebook = 'notebook',
Expand Down
31 changes: 25 additions & 6 deletions src/vs/platform/actionWidget/browser/actionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1881,29 +1881,48 @@ export class ActionListWidget<T> extends Disposable {

// Position: prefer right side, fall back to left if not enough space
const viewportWidth = targetWindow.innerWidth;
const viewportHeight = targetWindow.innerHeight;
const spaceRight = viewportWidth - anchorRect.right;
const spaceLeft = parentRect.left;
const panelWidth = maxWidth + 10; // account for border/padding

const margin = 8;
const gap = 4;
let containerLeft: number;
if (spaceRight >= panelWidth || spaceRight >= spaceLeft) {
this._submenuContainer.style.left = `${parentRect.right - parentRect.left + gap}px`;
containerLeft = parentRect.right - parentRect.left + gap;
} else {
this._submenuContainer.style.left = `${-panelWidth - gap}px`;
containerLeft = -panelWidth - gap;
}
// Clamp horizontally so the panel stays within the target window even
// when it's small (e.g. the floating chat input window), instead of
// spilling off the edge and being clipped.
let viewportLeft = parentRect.left + containerLeft;
if (viewportLeft + panelWidth > viewportWidth - margin) {
viewportLeft = viewportWidth - margin - panelWidth;
}
if (viewportLeft < margin) {
viewportLeft = margin;
}
this._submenuContainer.style.left = `${viewportLeft - parentRect.left}px`;

const hoverHeaderHeight = hoverHeader ? hoverHeader.offsetHeight : 0;
const totalPanelHeight = totalHeight + hoverHeaderHeight;
const viewportHeight = targetWindow.innerHeight;
const anchorHeight = anchorRect.height;
let top = anchorRect.top - parentRect.top + (anchorHeight - totalPanelHeight) / 2;
const panelBottom = parentRect.top + top + totalPanelHeight;
if (panelBottom > viewportHeight) {
top -= (panelBottom - viewportHeight + 8);
top -= (panelBottom - viewportHeight + margin);
}
if (parentRect.top + top < 0) {
top = -parentRect.top;
if (parentRect.top + top < margin) {
top = margin - parentRect.top;
}
this._submenuContainer.style.top = `${top}px`;
// Constrain the panel to the window and let overflowing content scroll,
// so a tall hover (e.g. model details) is never cut off by the bounds.
this._submenuContainer.style.maxHeight = `${viewportHeight - margin * 2}px`;
this._submenuContainer.style.overflowY = 'auto';
this._submenuContainer.style.overflowX = 'hidden';
}

private _hideSubmenu(): void {
Expand Down
7 changes: 5 additions & 2 deletions src/vs/platform/actionWidget/browser/actionWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { KeybindingWeight } from '../../keybinding/common/keybindingsRegistry.js
import { inputActiveOptionBackground, registerColor } from '../../theme/common/colorRegistry.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { IListAccessibilityProvider } from '../../../base/browser/ui/list/listWidget.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';

registerColor(
'actionBar.toggledBackground',
Expand Down Expand Up @@ -76,7 +77,8 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
constructor(
@IContextViewService private readonly _contextViewService: IContextViewService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILayoutService private readonly _layoutService: ILayoutService,
) {
super();
}
Expand All @@ -85,6 +87,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);

const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider, listOptions, anchor);
const targetContainer = container ?? (dom.isHTMLElement(anchor) ? this._layoutService.getContainer(dom.getWindow(anchor)) : undefined);
this._contextViewService.showContextView({
getAnchor: () => anchor,
render: (container: HTMLElement) => {
Expand All @@ -96,7 +99,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
this._onWidgetClosed(didCancel);
},
get anchorPosition() { return list.anchorPosition; },
}, container, false);
}, targetContainer, false);
}

acceptSelected(preview?: boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
this.lifecycleMainService.registerAuxWindow(this);

// Hide macOS traffic light buttons for frameless windows
if (isMacintosh && options?.frame === false) {
window.setWindowButtonVisibility(false);
if (options?.frame === false) {
window.setMinimumSize(1, 1);
if (isMacintosh) {
window.setWindowButtonVisibility(false);
}
}

// Disable resizing for non-resizable windows
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/windows/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt
options.frame = false;
options.titleBarStyle = undefined;
options.titleBarOverlay = undefined;
options.minWidth = undefined;
options.minHeight = undefined;
options.minWidth = 1;
options.minHeight = 1;
}

if (overrides?.backgroundColor) {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/sessions/SESSIONS_LIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The sessions list is the primary navigation surface in the Agents Window. It occ

The sessions list (`SessionsView` + `SessionsList`) displays every session known to `ISessionsManagementService`. Sessions are aggregated from all registered providers and shown in collapsible **sections**. The user can group, sort, filter, pin, and archive sessions. Selecting a session navigates to it.

When `chat.omni.enabled` is enabled, the Sessions header includes a `Codicon.commentDiscussionSparkle` action after **New Session** that toggles the floating chat input window.

### Key Files

| File | Purpose |
Expand Down
20 changes: 20 additions & 0 deletions src/vs/sessions/contrib/sessions/browser/sessions.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
import { IViewDescriptor, IViewsRegistry, Extensions as ViewContainerExtensions, WindowEnablement, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from '../../../../workbench/common/views.js';
import { localize, localize2 } from '../../../../nls.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { MenuRegistry } from '../../../../platform/actions/common/actions.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
Expand All @@ -20,6 +22,10 @@ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { SESSIONS_LIST_SHOW_EMPTY_DEFAULT_GROUPS_SETTING } from './views/sessionsList.js';
import { SessionsMouseNavigationContribution } from './sessionsMouseNavigation.js';
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
import { CHAT_INPUT_WINDOW_TOGGLE_COMMAND_ID } from '../../../../workbench/contrib/chat/common/chatInputWindow.js';
import { OmniChatEnabledSettingId } from '../../../../workbench/contrib/chat/common/sessionRouter.js';
import { Menus } from '../../../browser/menus.js';

const agentSessionsViewIcon = registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, localize('agentSessionsViewIcon', 'Icon for Agent Sessions View'));
const AGENT_SESSIONS_VIEW_TITLE = localize2('agentSessions.view.label', "Sessions");
Expand Down Expand Up @@ -56,6 +62,20 @@ const sessionsViewPaneDescriptor: IViewDescriptor = {

Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([sessionsViewPaneDescriptor], agentSessionsViewContainer);

MenuRegistry.appendMenuItem(Menus.SidebarSessionsHeader, {
command: {
id: CHAT_INPUT_WINDOW_TOGGLE_COMMAND_ID,
title: localize2('chat.toggleInputWindow', "Toggle Floating Chat Input Window"),
icon: Codicon.commentDiscussionSparkle,
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(
ChatContextKeys.enabled,
ContextKeyExpr.equals(`config.${OmniChatEnabledSettingId}`, true)
),
});

Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
id: 'sessions',
properties: {
Expand Down
1 change: 1 addition & 0 deletions src/vs/sessions/sessions.common.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ import '../workbench/contrib/speech/browser/speech.contribution.js';

// Chat
import '../workbench/contrib/chat/browser/chat.shared.contribution.js';
import '../workbench/contrib/chat/browser/chatInputWindow/chatInputWindow.contribution.js';
//import '../workbench/contrib/inlineChat/browser/inlineChat.contribution.js';
import '../workbench/contrib/mcp/browser/mcp.contribution.js';
import '../workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.js';
Expand Down
Loading
Loading