Skip to content

Commit 373f89e

Browse files
authored
Merge branch 'main' into copilot/fix-259672
2 parents 1294f72 + cbfcac6 commit 373f89e

File tree

5 files changed

+96
-32
lines changed

5 files changed

+96
-32
lines changed

src/vs/platform/terminal/node/terminalProfiles.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ async function detectAvailableWindowsProfiles(
8484
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
8585
const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`;
8686

87-
const useWSLexe = getWindowsBuildNumber() >= 22000;
87+
// WSL 2 released in the May 2020 Update, this is where the `-d` flag was added that we depend
88+
// upon
89+
const allowWslDiscovery = getWindowsBuildNumber() >= 19041;
8890

8991
await initializeWindowsProfiles(testPwshSourcePaths);
9092

@@ -143,9 +145,9 @@ async function detectAvailableWindowsProfiles(
143145

144146
const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, shellEnv, logService, variableResolver);
145147

146-
if (includeDetectedProfiles && useWslProfiles) {
148+
if (includeDetectedProfiles && useWslProfiles && allowWslDiscovery) {
147149
try {
148-
const result = await getWslProfiles(`${system32Path}\\${useWSLexe ? 'wsl' : 'bash'}.exe`, defaultProfileName);
150+
const result = await getWslProfiles(`${system32Path}\\wsl.exe`, defaultProfileName);
149151
for (const wslProfile of result) {
150152
if (!configProfiles || !(wslProfile.profileName in configProfiles)) {
151153
resultProfiles.push(wslProfile);

src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,6 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand
151151
const instanceOnDidFocusListener = instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true));
152152
const instanceOnDidBlurListener = instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset());
153153

154-
this._register(toDisposable(() => {
155-
if (!this._isDetached && !this._isShuttingDown) {
156-
// Will be ignored if triggered by onExit or onDisposed terminal events
157-
// as disposed was already called
158-
instance.dispose(TerminalExitReason.User);
159-
}
160-
dispose([instanceOnDidFocusListener, instanceOnDidBlurListener]);
161-
}));
162-
163154
const disposeListeners = [
164155
instance.onExit((e) => {
165156
if (!instance.waitOnExit) {
@@ -174,9 +165,19 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand
174165
instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
175166
];
176167

168+
this._register(toDisposable(() => {
169+
if (!this._isDetached && !this._isShuttingDown) {
170+
// Will be ignored if triggered by onExit or onDisposed terminal events
171+
// as disposed was already called
172+
instance.dispose(TerminalExitReason.User);
173+
}
174+
dispose(disposeListeners);
175+
dispose([instanceOnDidFocusListener, instanceOnDidBlurListener]);
176+
}));
177+
177178
// Don't dispose editor when instance is torn down on shutdown to avoid extra work and so
178179
// the editor/tabs don't disappear
179-
this._lifecycleService.onWillShutdown((e: WillShutdownEvent) => {
180+
this._register(this._lifecycleService.onWillShutdown((e: WillShutdownEvent) => {
180181
this._isShuttingDown = true;
181182
dispose(disposeListeners);
182183

@@ -187,7 +188,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand
187188
} else {
188189
instance.dispose(TerminalExitReason.Shutdown);
189190
}
190-
});
191+
}));
191192
}
192193

193194
override getName() {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { CancellationToken } from '../../../../../base/common/cancellation.js';
7+
import { Emitter, Event } from '../../../../../base/common/event.js';
8+
import { Disposable } from '../../../../../base/common/lifecycle.js';
9+
import { ILanguageModelsService } from '../../../chat/common/languageModels.js';
10+
import { IChatService } from '../../../chat/common/chatService.js';
11+
import { racePollingOrPrompt, promptForMorePolling, pollForOutputAndIdle } from './bufferOutputPolling.js';
12+
13+
export interface IOutputMonitor extends Disposable {
14+
readonly isIdle: boolean;
15+
16+
readonly onDidFinishCommand: Event<void>;
17+
readonly onDidIdle: Event<void>;
18+
readonly onDidTimeout: Event<void>;
19+
20+
startMonitoring(
21+
chatService: IChatService,
22+
command: string,
23+
invocationContext: any,
24+
token: CancellationToken
25+
): Promise<{ terminalExecutionIdleBeforeTimeout: boolean; output: string; pollDurationMs?: number; modelOutputEvalResponse?: string }>;
26+
}
27+
28+
export class OutputMonitor extends Disposable implements IOutputMonitor {
29+
private _isIdle = false;
30+
31+
private readonly _onDidFinishCommand = this._register(new Emitter<void>());
32+
readonly onDidFinishCommand = this._onDidFinishCommand.event;
33+
private readonly _onDidIdle = this._register(new Emitter<void>());
34+
readonly onDidIdle = this._onDidIdle.event;
35+
private readonly _onDidTimeout = this._register(new Emitter<void>());
36+
readonly onDidTimeout = this._onDidTimeout.event;
37+
38+
get isIdle(): boolean {
39+
return this._isIdle;
40+
}
41+
42+
constructor(
43+
private readonly _execution: { getOutput: () => string; isActive?: () => Promise<boolean> },
44+
@ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService
45+
) {
46+
super();
47+
}
48+
49+
async startMonitoring(
50+
chatService: IChatService,
51+
command: string,
52+
invocationContext: any,
53+
token: CancellationToken
54+
): Promise<{ terminalExecutionIdleBeforeTimeout: boolean; output: string; pollDurationMs?: number; modelOutputEvalResponse?: string }> {
55+
let result = await pollForOutputAndIdle(this._execution, false, token, this._languageModelsService);
56+
57+
if (!result.terminalExecutionIdleBeforeTimeout) {
58+
result = await racePollingOrPrompt(
59+
() => pollForOutputAndIdle(this._execution, true, token, this._languageModelsService),
60+
() => promptForMorePolling(command, token, invocationContext, chatService),
61+
result,
62+
token,
63+
this._languageModelsService,
64+
this._execution
65+
);
66+
}
67+
68+
return result;
69+
}
70+
}

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ import { ITerminalLogService } from '../../../../../../platform/terminal/common/
2222
import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js';
2323
import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js';
2424
import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js';
25-
import { ILanguageModelsService } from '../../../../chat/common/languageModels.js';
2625
import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolProgress, type IToolConfirmationAction, type IToolConfirmationMessages } from '../../../../chat/common/languageModelToolsService.js';
2726
import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js';
2827
import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerminal.js';
2928
import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js';
3029
import { getRecommendedToolsOverRunInTerminal } from '../alternativeRecommendation.js';
31-
import { getOutput, pollForOutputAndIdle, promptForMorePolling, racePollingOrPrompt } from '../bufferOutputPolling.js';
30+
import { getOutput } from '../bufferOutputPolling.js';
3231
import { CommandLineAutoApprover, type ICommandApprovalResultWithReason } from '../commandLineAutoApprover.js';
3332
import { BasicExecuteStrategy } from '../executeStrategy/basicExecuteStrategy.js';
3433
import type { ITerminalExecuteStrategy } from '../executeStrategy/executeStrategy.js';
@@ -38,6 +37,7 @@ import { isPowerShell } from '../runInTerminalHelpers.js';
3837
import { extractInlineSubCommands, splitCommandLineIntoSubCommands } from '../subCommands.js';
3938
import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js';
4039
import { Codicon } from '../../../../../../base/common/codicons.js';
40+
import { OutputMonitor } from '../outputMonitor.js';
4141
import type { TerminalNewAutoApproveButtonData } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolSubPart.js';
4242

4343
const TERMINAL_SESSION_STORAGE_KEY = 'chat.terminalSessions';
@@ -159,7 +159,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
159159
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
160160
@IChatService private readonly _chatService: IChatService,
161161
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
162-
@ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService
163162
) {
164163
super();
165164

@@ -414,31 +413,21 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
414413

415414
if (args.isBackground) {
416415
let outputAndIdle: { terminalExecutionIdleBeforeTimeout: boolean; output: string; pollDurationMs?: number; modelOutputEvalResponse?: string } | undefined = undefined;
416+
let outputMonitor: OutputMonitor | undefined = undefined;
417417
try {
418418
this._logService.debug(`RunInTerminalTool: Starting background execution \`${command}\``);
419419

420420
const execution = new BackgroundTerminalExecution(toolTerminal.instance, xterm, command);
421421
RunInTerminalTool._backgroundExecutions.set(termId, execution);
422422

423-
outputAndIdle = await pollForOutputAndIdle(execution, false, token, this._languageModelsService);
423+
outputMonitor = this._instantiationService.createInstance(OutputMonitor, execution);
424+
store.add(outputMonitor);
425+
426+
outputAndIdle = await outputMonitor.startMonitoring(this._chatService, command, invocation.context!, token);
424427
if (token.isCancellationRequested) {
425428
throw new CancellationError();
426429
}
427430

428-
if (!outputAndIdle.terminalExecutionIdleBeforeTimeout) {
429-
outputAndIdle = await racePollingOrPrompt(
430-
() => pollForOutputAndIdle(execution, true, token, this._languageModelsService),
431-
() => promptForMorePolling(command, token, invocation.context!, this._chatService),
432-
outputAndIdle,
433-
token,
434-
this._languageModelsService,
435-
execution
436-
);
437-
if (token.isCancellationRequested) {
438-
throw new CancellationError();
439-
}
440-
}
441-
442431
let resultText = (
443432
didUserEditCommand
444433
? `Note: The user manually edited the command to \`${command}\`, and that command is now running in terminal with ID=${termId}`

src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
122122
du: true,
123123
df: true,
124124
jq: true,
125+
sleep: true,
126+
'Start-Sleep': true,
125127
// While these PowerShell verbs can have side effects, they are generally innocuous (eg.
126128
// updating OS-level file access info) and and often have prompts if they're more
127129
// involved (eg. Get-Credential)

0 commit comments

Comments
 (0)