Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion src/vs/code/electron-main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,10 +895,19 @@ export class CodeApplication extends Disposable {
private async handleProtocolUrl(windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService, urlService: IURLService, uri: URI, options?: IOpenURLOptions): Promise<boolean> {
this.logService.trace('app#handleProtocolUrl():', uri.toString(true), options);

// Sessions app: "open a sessions window", regardless of other parameters.
if ((process as INodeProcess).isEmbeddedApp) {
this.logService.trace('app#handleProtocolUrl() opening sessions window for bare protocol URL:', uri.toString(true));

await windowsMainService.openSessionsWindow({ context: OpenContext.LINK, contextWindowId: undefined });

return true;
}

// Support 'workspace' URLs (https://github.com/microsoft/vscode/issues/124263)
if (uri.scheme === this.productService.urlProtocol && uri.path === 'workspace') {
uri = uri.with({
authority: 'file',
authority: Schemas.file,
path: URI.parse(uri.query).path,
query: ''
});
Expand Down
3 changes: 1 addition & 2 deletions src/vs/platform/browserView/electron-main/browserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import { IApplicationStorageMainService } from '../../storage/electron-main/stor
import { BrowserViewStorageScope } from '../common/browserView.js';
import { BrowserSessionTrust, IBrowserSessionTrust } from './browserSessionTrust.js';

// Same as webviews
// Same as webviews, minus clipboard-read
const allowedPermissions = new Set([
'pointerLock',
'notifications',
'clipboard-read',
'clipboard-sanitized-write'
]);

Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/extensions/common/extensionHostStarter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IExtensionHostStarter = createDecorator<IExtensionHostStarter>('extensionHostStarter');

export const ipcExtensionHostStarterChannelName = 'extensionHostStarter';
export const extensionHostGraceTimeMs = 6000;

export interface IExtensionHostProcessOptions {
responseWindowId: number;
Expand All @@ -31,6 +32,7 @@ export interface IExtensionHostStarter {
createExtensionHost(): Promise<{ id: string }>;
start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }>;
enableInspectPort(id: string): Promise<boolean>;
waitForExit(id: string, maxWaitTimeMs: number): Promise<void>;
kill(id: string): Promise<void>;

}
15 changes: 13 additions & 2 deletions src/vs/platform/extensions/electron-main/extensionHostStarter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Promises } from '../../../base/common/async.js';
import { canceled } from '../../../base/common/errors.js';
import { Event } from '../../../base/common/event.js';
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from '../common/extensionHostStarter.js';
import { extensionHostGraceTimeMs, IExtensionHostProcessOptions, IExtensionHostStarter } from '../common/extensionHostStarter.js';
import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMainService.js';
import { ILogService } from '../../log/common/log.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
Expand Down Expand Up @@ -121,7 +121,7 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx
allowLoadingUnsignedLibraries: true,
respondToAuthRequestsFromMainProcess: true,
windowLifecycleBound: true,
windowLifecycleGraceTime: 6000,
windowLifecycleGraceTime: extensionHostGraceTimeMs,
correlationId: id
});
const pid = await Event.toPromise(extHost.onSpawn);
Expand Down Expand Up @@ -151,6 +151,17 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx
extHostProcess.kill();
}

async waitForExit(id: string, maxWaitTimeMs: number): Promise<void> {
if (this._shutdown) {
throw canceled();
}
const extHostProcess = this._extHosts.get(id);
if (!extHostProcess) {
return;
}
await extHostProcess.waitForExit(maxWaitTimeMs);
}

async _killAllNow(): Promise<void> {
for (const [, extHost] of this._extHosts) {
extHost.kill();
Expand Down
4 changes: 2 additions & 2 deletions src/vs/sessions/contrib/sessions/browser/sessionsViewPane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ export class AgenticSessionsViewPane extends ViewPane {
const sessionsFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, {
filterMenuId: SessionsViewFilterSubMenu,
groupResults: () => this.currentGrouping,
allowedProviders: undefined, // TODO: restore to [AgentSessionProviders.Background, AgentSessionProviders.Cloud]
allowedProviders: [AgentSessionProviders.Background, AgentSessionProviders.Cloud],
providerLabelOverrides: new Map([
[AgentSessionProviders.Background, localize('chat.session.providerLabel.local', "Local")],
[AgentSessionProviders.Background, localize('chat.session.providerLabel.background', "Copilot CLI")],
]),
}));

Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/api/common/extHostChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,11 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
}

try {
provider.provider.provideHandleOptionsChange(sessionResource, updates, token);
const updatesToSend = updates.map(update => ({
optionId: update.optionId,
value: update.value === undefined ? undefined : (typeof update.value === 'string' ? update.value : update.value.id)
}));
await provider.provider.provideHandleOptionsChange(sessionResource, updatesToSend, token);
} catch (error) {
this._logService.error(`Error calling provideHandleOptionsChange for handle ${handle}, sessionResource ${sessionResource}:`, error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { h } from '../../../../../../../base/browser/dom.js';
import { renderLabelWithIcons } from '../../../../../../../base/browser/ui/iconLabel/iconLabels.js';
import { ActionBar } from '../../../../../../../base/browser/ui/actionbar/actionbar.js';
import { isMarkdownString, MarkdownString } from '../../../../../../../base/common/htmlContent.js';
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
Expand Down Expand Up @@ -499,6 +500,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart
const wrapper = this._register(this._instantiationService.createInstance(
ChatTerminalThinkingCollapsibleWrapper,
truncatedCommand,
this._terminalData.commandLine.isSandboxWrapped === true,
contentElement,
context,
initialExpanded,
Expand Down Expand Up @@ -1626,10 +1628,12 @@ export class ContinueInBackgroundAction extends Action implements IAction {
class ChatTerminalThinkingCollapsibleWrapper extends ChatCollapsibleContentPart {
private readonly _terminalContentElement: HTMLElement;
private readonly _commandText: string;
private readonly _isSandboxWrapped: boolean;
private _isComplete: boolean;

constructor(
commandText: string,
isSandboxWrapped: boolean,
contentElement: HTMLElement,
context: IChatContentPartRenderContext,
initialExpanded: boolean,
Expand All @@ -1642,6 +1646,7 @@ class ChatTerminalThinkingCollapsibleWrapper extends ChatCollapsibleContentPart

this._terminalContentElement = contentElement;
this._commandText = commandText;
this._isSandboxWrapped = isSandboxWrapped;
this._isComplete = isComplete;

this.domNode.classList.add('chat-terminal-thinking-collapsible');
Expand All @@ -1661,6 +1666,12 @@ class ChatTerminalThinkingCollapsibleWrapper extends ChatCollapsibleContentPart

const labelElement = this._collapseButton.labelElement;
labelElement.textContent = '';
if (this._isSandboxWrapped) {
dom.reset(labelElement, ...renderLabelWithIcons(this._isComplete
? localize('chat.terminal.ranInSandbox', "$(lock) Ran `{0}` in sandbox", this._commandText)
: localize('chat.terminal.runningInSandbox', "$(lock) Running `{0}` in sandbox", this._commandText)));
return;
}

const prefixText = this._isComplete
? localize('chat.terminal.ran.prefix', "Ran ")
Expand Down
42 changes: 40 additions & 2 deletions src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { IChatTodoListService } from '../../common/tools/chatTodoListService.js'
import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isWorkspaceVariableEntry, PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/attachments/chatVariableEntries.js';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/model/chatViewModel.js';
import { CodeBlockModelCollection } from '../../common/widget/codeBlockModelCollection.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel } from '../../common/constants.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel, ThinkingDisplayMode } from '../../common/constants.js';
import { ILanguageModelToolsService, isToolSet } from '../../common/tools/languageModelToolsService.js';
import { ComputeAutomaticInstructions } from '../../common/promptSyntax/computeAutomaticInstructions.js';
import { IHandOff, PromptHeader } from '../../common/promptSyntax/promptFileParser.js';
Expand Down Expand Up @@ -173,6 +173,20 @@ type ChatPromptRunClassification = {
promptNameHash?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Hashed name of local or user prompt for privacy.' };
};

type ChatThinkingStyleUsageEvent = {
thinkingStyle: ThinkingDisplayMode;
location: ChatAgentLocation;
requestKind: 'submit' | 'rerun';
};

type ChatThinkingStyleUsageClassification = {
owner: 'justschen';
comment: 'Event fired when a chat request uses the configured thinking style rendering mode.';
thinkingStyle: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The configured rendering mode for thinking content.' };
location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The location where the request was made.' };
requestKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the request was a new submit or a rerun.' };
};

const supportsAllAttachments: Required<IChatAgentAttachmentCapabilities> = {
supportsFileAttachments: true,
supportsToolAttachments: true,
Expand Down Expand Up @@ -2211,7 +2225,29 @@ export class ChatWidget extends Disposable implements IChatWidget {
userSelectedModelId: this.input.currentLanguageModel,
modeInfo: this.input.currentModeInfo,
};
return await this.chatService.resendRequest(lastRequest, options);
const result = await this.chatService.resendRequest(lastRequest, options);
this.logThinkingStyleUsage('rerun');
return result;
}

private getConfiguredThinkingStyle(): ThinkingDisplayMode {
const thinkingStyle = this.configurationService.getValue<ThinkingDisplayMode>(ChatConfiguration.ThinkingStyle);
switch (thinkingStyle) {
case ThinkingDisplayMode.Collapsed:
case ThinkingDisplayMode.CollapsedPreview:
case ThinkingDisplayMode.FixedScrolling:
return thinkingStyle;
default:
return ThinkingDisplayMode.FixedScrolling;
}
}

private logThinkingStyleUsage(requestKind: ChatThinkingStyleUsageEvent['requestKind']): void {
this.telemetryService.publicLog2<ChatThinkingStyleUsageEvent, ChatThinkingStyleUsageClassification>('chat.thinkingStyleUsage', {
thinkingStyle: this.getConfiguredThinkingStyle(),
location: this.location,
requestKind,
});
}

private async _applyPromptFileIfSet(requestInput: IChatRequestInputOptions): Promise<void> {
Expand Down Expand Up @@ -2396,6 +2432,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
return;
}

this.logThinkingStyleUsage('submit');

// visibility sync before firing events to hide the welcome view
this.updateChatViewVisibility();
this.input.acceptInput(options?.storeToHistory ?? isUserQuery);
Expand Down
13 changes: 13 additions & 0 deletions src/vs/workbench/contrib/chat/browser/widget/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ have to be updated for changes to the rules above, or to support more deeply nes

.interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container,
.interactive-input-part:has(.chat-todo-list-widget-container > .chat-todo-list-widget.has-todos) .chat-input-container,
.interactive-input-part:has(.chat-artifacts-widget-container > .chat-artifacts-widget) .chat-input-container,
.interactive-input-part:has(.chat-input-widgets-container > .chat-status-widget:not([style*="display: none"])) .chat-input-container,
.interactive-input-part:has(.chat-getting-started-tip-container > .chat-tip-widget) .chat-input-container {
/* Remove top border radius when editing session, todo list, or status widget is present */
Expand Down Expand Up @@ -883,6 +884,12 @@ have to be updated for changes to the rules above, or to support more deeply nes
border-top-right-radius: 0;
}

.interactive-session .interactive-input-part > .chat-artifacts-widget-container + .chat-editing-session .chat-editing-session-container {
border-top-left-radius: 0;
border-top-right-radius: 0;
}


.interactive-session .chat-editing-session .monaco-list-row .chat-collapsible-list-action-bar {
padding-left: 5px;
display: none;
Expand Down Expand Up @@ -1106,6 +1113,12 @@ have to be updated for changes to the rules above, or to support more deeply nes
position: relative;
}


.interactive-session .interactive-input-part > .chat-artifacts-widget-container:empty {
display: none;
}


/* Chat Todo List Widget Container - mirrors chat-editing-session styling */
.interactive-session .interactive-input-part > .chat-todo-list-widget-container {
margin-bottom: -4px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import { ChatEntitlementContextKeys } from '../../../../services/chat/common/cha
import { CHAT_CATEGORY } from '../../browser/actions/chatActions.js';
import { ProductQualityContext } from '../../../../../platform/contextkey/common/contextkeys.js';
import { IsSessionsWindowContext } from '../../../../common/contextkeys.js';
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
import { IProductService } from '../../../../../platform/product/common/productService.js';
import { URI } from '../../../../../base/common/uri.js';
import { isMacintosh, isWindows } from '../../../../../base/common/platform.js';
import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js';
import { Schemas } from '../../../../../base/common/network.js';

export class OpenSessionsWindowAction extends Action2 {
constructor() {
Expand All @@ -24,7 +30,21 @@ export class OpenSessionsWindowAction extends Action2 {
}

async run(accessor: ServicesAccessor) {
const nativeHostService = accessor.get(INativeHostService);
await nativeHostService.openSessionsWindow();
const openerService = accessor.get(IOpenerService);
const productService = accessor.get(IProductService);
const environmentService = accessor.get(IWorkbenchEnvironmentService);

if (environmentService.isBuilt && (isMacintosh || isWindows)) {
const scheme = productService.quality === 'stable'
? 'vscode-sessions'
: productService.quality === 'exploration'
? 'vscode-sessions-exploration'
: 'vscode-sessions-insiders';

await openerService.open(URI.from({ scheme, authority: Schemas.file }), { openExternal: true });
} else {
const nativeHostService = accessor.get(INativeHostService);
await nativeHostService.openSessionsWindow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface IOutputAnalyzerOptions {
readonly exitCode: number | undefined;
readonly exitResult: string;
readonly commandLine: string;
readonly isSandboxWrapped: boolean;
}

export interface IOutputAnalyzer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,9 +770,13 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
? rawDisplayCommand.substring(0, 77) + '...'
: rawDisplayCommand;
const escapedDisplayCommand = escapeMarkdownSyntaxTokens(displayCommand);
const invocationMessage = args.isBackground
? new MarkdownString(localize('runInTerminal.invocation.background', "Running `{0}` in background", escapedDisplayCommand))
: new MarkdownString(localize('runInTerminal.invocation', "Running `{0}`", escapedDisplayCommand));
const invocationMessage = toolSpecificData.commandLine.isSandboxWrapped
? new MarkdownString(args.isBackground
? localize('runInTerminal.invocation.sandbox.background', "$(lock) Running `{0}` in sandbox in background", escapedDisplayCommand)
: localize('runInTerminal.invocation.sandbox', "$(lock) Running `{0}` in sandbox", escapedDisplayCommand), { supportThemeIcons: true })
: new MarkdownString(args.isBackground
? localize('runInTerminal.invocation.background', "Running `{0}` in background", escapedDisplayCommand)
: localize('runInTerminal.invocation', "Running `{0}`", escapedDisplayCommand));

return {
invocationMessage,
Expand Down Expand Up @@ -1198,7 +1202,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
}
let outputAnalyzerMessage: string | undefined;
for (const analyzer of this._outputAnalyzers) {
const message = await analyzer.analyze({ exitCode, exitResult: terminalResult, commandLine: command });
const message = await analyzer.analyze({ exitCode, exitResult: terminalResult, commandLine: command, isSandboxWrapped: didSandboxWrapCommand });
if (message) {
outputAnalyzerMessage = message;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../../../../nls.js';
import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { OperatingSystem } from '../../../../../../base/common/platform.js';
import { ITerminalSandboxService } from '../../common/terminalSandboxService.js';
import type { IOutputAnalyzer, IOutputAnalyzerOptions } from './outputAnalyzer.js';
import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js';
Expand All @@ -20,16 +20,18 @@ export class SandboxOutputAnalyzer extends Disposable implements IOutputAnalyzer
if (options.exitCode === undefined || options.exitCode === 0) {
return undefined;
}
if (!(await this._sandboxService.isEnabled())) {
if (!options.isSandboxWrapped) {
return undefined;
}

return localize(
'runInTerminalTool.sandboxCommandFailed',
"Command failed while running in sandboxed mode. Use the command result to determine the scenario. If the issue is filesystem permissions, update allowWrite in {0} (Linux) or {1} (macOS). If the issue is domain/network related, add the required domains to {2}.allowedDomains.",
TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem,
TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem,
TerminalChatAgentToolsSettingId.TerminalSandboxNetwork
);
const os = await this._sandboxService.getOS();
const fileSystemSetting = os === OperatingSystem.Linux
? TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem
: TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem;
return `Command failed while running in sandboxed mode. If the command failed due to sandboxing:
- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${fileSystemSetting}, or to add required domains to ${TerminalChatAgentToolsSettingId.TerminalSandboxNetwork}.allowedDomains.
- You can also rerun requestUnsandboxedExecution=true and prompt the user to bypass the sandbox.

Here is the output of the command:\n`;
}
}
Loading
Loading