Skip to content

Commit 8d83e96

Browse files
committed
Handle marker disposable when fetching tool output
Fixes microsoft#257129
1 parent 01048a9 commit 8d83e96

File tree

3 files changed

+72
-11
lines changed

3 files changed

+72
-11
lines changed

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { CancellationToken } from '../../../../../../base/common/cancellati
77
import { CancellationError } from '../../../../../../base/common/errors.js';
88
import { Event } from '../../../../../../base/common/event.js';
99
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
10+
import { isNumber } from '../../../../../../base/common/types.js';
1011
import type { ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js';
1112
import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js';
1213
import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js';
@@ -80,14 +81,22 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy {
8081
this._logService.debug('RunInTerminalTool#Basic: Waiting for idle');
8182
await waitForIdle(this._instance.onData, 1000);
8283

84+
// Record where the command started. If the marker gets disposed, re-created it where
85+
// the cursor is. This can happen in prompts where they clear the line and rerender it
86+
// like powerlevel10k's transient prompt
87+
let startMarker = store.add(xterm.raw.registerMarker());
88+
store.add(startMarker.onDispose(() => {
89+
this._logService.debug(`RunInTerminalTool#Basic: Start marker was disposed, recreating`);
90+
startMarker = xterm.raw.registerMarker();
91+
}));
92+
8393
// Execute the command
84-
const startMarker = store.add(xterm.raw.registerMarker());
8594
this._logService.debug(`RunInTerminalTool#Basic: Executing command line \`${commandLine}\``);
8695
this._instance.runCommand(commandLine, true);
8796

8897
// Wait for the next end execution event - note that this may not correspond to the actual
8998
// execution requested
90-
const doneData = await onDone;
99+
const finishedCommand = await onDone;
91100

92101
// Wait for the terminal to idle
93102
this._logService.debug('RunInTerminalTool#Basic: Waiting for idle');
@@ -98,13 +107,36 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy {
98107
const endMarker = store.add(xterm.raw.registerMarker());
99108

100109
// Assemble final result
101-
let result = xterm.getContentsAsText(startMarker, endMarker);
102-
if (doneData && typeof doneData.exitCode === 'number' && doneData.exitCode > 0) {
103-
result += `\n\nCommand exited with code ${doneData.exitCode}`;
110+
let result: string | undefined;
111+
if (finishedCommand) {
112+
const commandOutput = finishedCommand?.getOutput();
113+
if (commandOutput !== undefined) {
114+
this._logService.debug('RunInTerminalTool#Basic: Fetched output via finished command');
115+
result = commandOutput;
116+
}
117+
}
118+
if (result === undefined) {
119+
try {
120+
result = xterm.getContentsAsText(startMarker, endMarker);
121+
this._logService.debug('RunInTerminalTool#Basic: Fetched output via markers');
122+
} catch {
123+
this._logService.debug('RunInTerminalTool#Basic: Failed to fetch output via markers');
124+
result = 'Failed to retrieve command output';
125+
}
126+
}
127+
128+
if (result.trim().length === 0) {
129+
result = 'Command produced no output';
104130
}
131+
132+
const exitCode = finishedCommand?.exitCode;
133+
if (isNumber(exitCode) && exitCode > 0) {
134+
result += `\n\nCommand exited with code ${exitCode}`;
135+
}
136+
105137
return {
106138
result,
107-
exitCode: doneData?.exitCode,
139+
exitCode,
108140
};
109141
} finally {
110142
store.dispose();

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,16 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy {
4646
throw new CancellationError();
4747
}
4848

49+
// Record where the command started. If the marker gets disposed, re-created it where
50+
// the cursor is. This can happen in prompts where they clear the line and rerender it
51+
// like powerlevel10k's transient prompt
52+
let startMarker = store.add(xterm.raw.registerMarker());
53+
store.add(startMarker.onDispose(() => {
54+
this._logService.debug(`RunInTerminalTool#Rich: Start marker was disposed, recreating`);
55+
startMarker = xterm.raw.registerMarker();
56+
}));
57+
4958
// Execute the command
50-
const startMarker = store.add(xterm.raw.registerMarker());
5159
this._logService.debug(`RunInTerminalTool#None: Executing command line \`${commandLine}\``);
5260
this._instance.runCommand(commandLine, true);
5361

@@ -60,8 +68,16 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy {
6068
const endMarker = store.add(xterm.raw.registerMarker());
6169

6270
// Assemble final result - exit code is not available without shell integration
71+
let result: string;
72+
try {
73+
result = xterm.getContentsAsText(startMarker, endMarker);
74+
this._logService.debug('RunInTerminalTool#None: Fetched output via markers');
75+
} catch {
76+
this._logService.debug('RunInTerminalTool#None: Failed to fetch output via markers');
77+
result = 'Failed to retrieve command output';
78+
}
6379
return {
64-
result: xterm.getContentsAsText(startMarker, endMarker),
80+
result,
6581
exitCode: undefined,
6682
};
6783
} finally {

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,17 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy {
5353
}),
5454
]);
5555

56+
// Record where the command started. If the marker gets disposed, re-created it where
57+
// the cursor is. This can happen in prompts where they clear the line and rerender it
58+
// like powerlevel10k's transient prompt
59+
let startMarker = store.add(xterm.raw.registerMarker());
60+
store.add(startMarker.onDispose(() => {
61+
this._logService.debug(`RunInTerminalTool#Rich: Start marker was disposed, recreating`);
62+
startMarker = xterm.raw.registerMarker();
63+
}));
64+
5665
// Execute the command
5766
this._logService.debug(`RunInTerminalTool#Rich: Executing command line \`${commandLine}\``);
58-
const startMarker = store.add(xterm.raw.registerMarker());
5967
this._instance.runCommand(commandLine, true);
6068

6169
this._logService.debug(`RunInTerminalTool#Rich: Waiting for done event`);
@@ -74,8 +82,13 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy {
7482
}
7583
}
7684
if (result === undefined) {
77-
this._logService.debug('RunInTerminalTool#Rich: Fetched output via markers');
78-
result = xterm.getContentsAsText(startMarker, endMarker);
85+
try {
86+
result = xterm.getContentsAsText(startMarker, endMarker);
87+
this._logService.debug('RunInTerminalTool#Rich: Fetched output via markers');
88+
} catch {
89+
this._logService.debug('RunInTerminalTool#Basic: Failed to fetch output via markers');
90+
result = 'Failed to retrieve command output';
91+
}
7992
}
8093

8194
if (result.trim().length === 0) {

0 commit comments

Comments
 (0)