Skip to content

Commit 8ee3a00

Browse files
authored
add output monitoring for foreground terminals (microsoft#263438)
1 parent f7361bc commit 8ee3a00

File tree

5 files changed

+41
-18
lines changed

5 files changed

+41
-18
lines changed

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
import type { CancellationToken } from '../../../../../../base/common/cancellation.js';
77
import { CancellationError } from '../../../../../../base/common/errors.js';
8-
import { Event } from '../../../../../../base/common/event.js';
8+
import { Emitter, Event } from '../../../../../../base/common/event.js';
99
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
1010
import { isNumber } from '../../../../../../base/common/types.js';
1111
import type { ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js';
1212
import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js';
1313
import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js';
1414
import { trackIdleOnPrompt, waitForIdle, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js';
15+
import type { IMarker as IXtermMarker } from '@xterm/xterm';
1516

1617
/**
1718
* This strategy is used when shell integration is enabled, but rich command detection was not
@@ -37,6 +38,10 @@ import { trackIdleOnPrompt, waitForIdle, type ITerminalExecuteStrategy, type ITe
3738
*/
3839
export class BasicExecuteStrategy implements ITerminalExecuteStrategy {
3940
readonly type = 'basic';
41+
private _startMarker: IXtermMarker | undefined;
42+
43+
private readonly _onDidCreateStartMarker = new Emitter<IXtermMarker | undefined>;
44+
public onDidCreateStartMarker: Event<IXtermMarker | undefined> = this._onDidCreateStartMarker.event;
4045

4146
constructor(
4247
private readonly _instance: ITerminalInstance,
@@ -84,10 +89,10 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy {
8489
// Record where the command started. If the marker gets disposed, re-created it where
8590
// the cursor is. This can happen in prompts where they clear the line and rerender it
8691
// like powerlevel10k's transient prompt
87-
let startMarker = store.add(xterm.raw.registerMarker());
88-
store.add(startMarker.onDispose(() => {
92+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
93+
store.add(this._startMarker.onDispose(() => {
8994
this._log(`Start marker was disposed, recreating`);
90-
startMarker = xterm.raw.registerMarker();
95+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
9196
}));
9297

9398
// Execute the command
@@ -122,7 +127,7 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy {
122127
}
123128
if (output === undefined) {
124129
try {
125-
output = xterm.getContentsAsText(startMarker, endMarker);
130+
output = xterm.getContentsAsText(this._startMarker, endMarker);
126131
this._log('Fetched output via markers');
127132
} catch {
128133
this._log('Failed to fetch output via markers');

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { CancellationToken } from '../../../../../../base/common/cancellati
88
import type { Event } from '../../../../../../base/common/event.js';
99
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
1010
import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js';
11+
import type { IMarker as IXtermMarker } from '@xterm/xterm';
1112

1213
export interface ITerminalExecuteStrategy {
1314
readonly type: 'rich' | 'basic' | 'none';
@@ -16,6 +17,8 @@ export interface ITerminalExecuteStrategy {
1617
* result will include information about the exit code.
1718
*/
1819
execute(commandLine: string, token: CancellationToken): Promise<ITerminalExecuteStrategyResult>;
20+
21+
onDidCreateStartMarker: Event<IXtermMarker | undefined>;
1922
}
2023

2124
export interface ITerminalExecuteStrategyResult {

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
import type { CancellationToken } from '../../../../../../base/common/cancellation.js';
77
import { CancellationError } from '../../../../../../base/common/errors.js';
8+
import { Emitter, Event } from '../../../../../../base/common/event.js';
89
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
910
import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js';
1011
import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js';
1112
import { waitForIdle, waitForIdleWithPromptHeuristics, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js';
13+
import type { IMarker as IXtermMarker } from '@xterm/xterm';
1214

1315
/**
1416
* This strategy is used when no shell integration is available. There are very few extension APIs
@@ -18,6 +20,10 @@ import { waitForIdle, waitForIdleWithPromptHeuristics, type ITerminalExecuteStra
1820
*/
1921
export class NoneExecuteStrategy implements ITerminalExecuteStrategy {
2022
readonly type = 'none';
23+
private _startMarker: IXtermMarker | undefined;
24+
25+
private readonly _onDidCreateStartMarker = new Emitter<IXtermMarker | undefined>;
26+
public onDidCreateStartMarker: Event<IXtermMarker | undefined> = this._onDidCreateStartMarker.event;
2127

2228
constructor(
2329
private readonly _instance: ITerminalInstance,
@@ -49,10 +55,10 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy {
4955
// Record where the command started. If the marker gets disposed, re-created it where
5056
// the cursor is. This can happen in prompts where they clear the line and rerender it
5157
// like powerlevel10k's transient prompt
52-
let startMarker = store.add(xterm.raw.registerMarker());
53-
store.add(startMarker.onDispose(() => {
58+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
59+
store.add(this._startMarker.onDispose(() => {
5460
this._log(`Start marker was disposed, recreating`);
55-
startMarker = xterm.raw.registerMarker();
61+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
5662
}));
5763

5864
// Execute the command
@@ -75,7 +81,7 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy {
7581
let output: string | undefined;
7682
const additionalInformationLines: string[] = [];
7783
try {
78-
output = xterm.getContentsAsText(startMarker, endMarker);
84+
output = xterm.getContentsAsText(this._startMarker, endMarker);
7985
this._log('Fetched output via markers');
8086
} catch {
8187
this._log('Failed to fetch output via markers');

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
import type { CancellationToken } from '../../../../../../base/common/cancellation.js';
77
import { CancellationError } from '../../../../../../base/common/errors.js';
8-
import { Event } from '../../../../../../base/common/event.js';
8+
import { Emitter, Event } from '../../../../../../base/common/event.js';
99
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
1010
import { isNumber } from '../../../../../../base/common/types.js';
1111
import type { ICommandDetectionCapability, ITerminalCommand } from '../../../../../../platform/terminal/common/capabilities/capabilities.js';
1212
import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js';
1313
import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js';
1414
import { trackIdleOnPrompt, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js';
15+
import type { IMarker as IXtermMarker } from '@xterm/xterm';
1516

1617
/**
1718
* This strategy is used when the terminal has rich shell integration/command detection is
@@ -22,6 +23,10 @@ import { trackIdleOnPrompt, type ITerminalExecuteStrategy, type ITerminalExecute
2223
*/
2324
export class RichExecuteStrategy implements ITerminalExecuteStrategy {
2425
readonly type = 'rich';
26+
private _startMarker: IXtermMarker | undefined;
27+
28+
private readonly _onDidCreateStartMarker = new Emitter<IXtermMarker | undefined>;
29+
public onDidCreateStartMarker: Event<IXtermMarker | undefined> = this._onDidCreateStartMarker.event;
2530

2631
constructor(
2732
private readonly _instance: ITerminalInstance,
@@ -56,10 +61,10 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy {
5661
// Record where the command started. If the marker gets disposed, re-created it where
5762
// the cursor is. This can happen in prompts where they clear the line and rerender it
5863
// like powerlevel10k's transient prompt
59-
let startMarker = store.add(xterm.raw.registerMarker());
60-
store.add(startMarker.onDispose(() => {
64+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
65+
store.add(this._startMarker.onDispose(() => {
6166
this._log(`Start marker was disposed, recreating`);
62-
startMarker = xterm.raw.registerMarker();
67+
this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker()));
6368
}));
6469

6570
// Execute the command
@@ -86,7 +91,7 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy {
8691
}
8792
if (output === undefined) {
8893
try {
89-
output = xterm.getContentsAsText(startMarker, endMarker);
94+
output = xterm.getContentsAsText(this._startMarker, endMarker);
9095
this._log('Fetched output via markers');
9196
} catch {
9297
this._log('Failed to fetch output via markers');

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
420420
inputUserSigint ||= data === '\x03';
421421
}));
422422

423+
let outputMonitor: OutputMonitor | undefined;
423424
if (args.isBackground) {
424-
let outputMonitor: OutputMonitor | undefined;
425425
let pollingResult: IPollingResult & { pollDurationMs: number } | undefined;
426426
try {
427427
this._logService.debug(`RunInTerminalTool: Starting background execution \`${command}\``);
@@ -517,7 +517,11 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
517517
}
518518
}
519519
this._logService.debug(`RunInTerminalTool: Using \`${strategy.type}\` execute strategy for command \`${command}\``);
520+
store.add(strategy.onDidCreateStartMarker(startMarker => {
521+
outputMonitor = store.add(this._instantiationService.createInstance(OutputMonitor, { instance: toolTerminal.instance, sessionId: invocation.context!.sessionId, getOutput: () => getOutput(toolTerminal.instance, startMarker) }, undefined, invocation.context!, token, command));
522+
}));
520523
const executeResult = await strategy.execute(command, token);
524+
521525
if (token.isCancellationRequested) {
522526
throw new CancellationError();
523527
}
@@ -560,9 +564,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
560564
inputUserSigint,
561565
terminalExecutionIdleBeforeTimeout: undefined,
562566
pollDurationMs: undefined,
563-
inputToolManualAcceptCount: 0,
564-
inputToolManualRejectCount: 0,
565-
inputToolManualChars: 0,
567+
inputToolManualAcceptCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualAcceptCount,
568+
inputToolManualRejectCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualRejectCount,
569+
inputToolManualChars: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualChars,
566570
});
567571
}
568572

0 commit comments

Comments
 (0)