Fix terminal output capture: prevent premature idle detection and handle partial command echoes

- setupRecreatingStartMarker returns IDisposable to stop marker recreation
  before sending commands (prevents marker jumping on PSReadLine re-renders)
- noneExecuteStrategy waits for cursor to move past start line after sendText
  before starting idle detection (prevents end marker at same line as start)
- findCommandEcho supports suffix matching for partial command echoes from
  wrapped getOutput() results (shell integration ON with long commands)
- Suffix matching requires mid-word split to avoid false positives on output
  that happens to be a suffix of the command (e.g. echo output)
- Integration tests: use ; separator on Windows, add && conversion test,
  handle Windows exit code quirks with cmd /c
This commit is contained in:
Alexandru Dima
2026-03-22 00:46:15 +01:00
parent 5059232618
commit 5563927f89
6 changed files with 111 additions and 14 deletions
@@ -208,11 +208,26 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
const m1 = `M1_${Date.now()}`;
const m2 = `M2_${Date.now()}`;
const m3 = `M3_${Date.now()}`;
const output = await invokeRunInTerminal(`echo ${m1} && echo ${m2} && echo ${m3}`);
// Use `;` on Windows (PowerShell) since `&&` is rewritten to `;`
const sep = isWindows ? ';' : '&&';
const output = await invokeRunInTerminal(`echo ${m1} ${sep} echo ${m2} ${sep} echo ${m3}`);
assert.strictEqual(output.trim(), `${m1}\n${m2}\n${m3}`);
});
(isWindows ? test : test.skip)('&& operators are converted to ; on PowerShell', async function () {
this.timeout(60000);
const m1 = `CHAIN_${Date.now()}_A`;
const m2 = `CHAIN_${Date.now()}_B`;
const output = await invokeRunInTerminal(`echo ${m1} && echo ${m2}`);
// The rewriter prepends a note explaining the simplification
const trimmed = output.trim();
assert.ok(trimmed.startsWith('Note: The tool simplified the command to'), `Expected rewrite note, got: ${trimmed}`);
assert.ok(trimmed.endsWith(`${m1}\n${m2}`), `Expected markers at end, got: ${trimmed}`);
});
test('non-zero exit code is reported', async function () {
this.timeout(60000);
@@ -220,10 +235,14 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
const command = isWindows ? 'cmd /c exit 42' : 'bash -c "exit 42"';
const output = await invokeRunInTerminal(command);
// Without shell integration, exit codes are unavailable
// Without shell integration, exit codes are unavailable.
// On Windows with shell integration, `cmd /c exit 42` may report
// exit code 1 instead of 42 due to how PowerShell propagates
// cmd.exe exit codes through shell integration sequences.
const acceptable = [
'Command produced no output\nCommand exited with code 42',
...(!hasShellIntegration ? ['Command produced no output'] : []),
...(isWindows && hasShellIntegration ? ['Command produced no output\nCommand exited with code 1'] : []),
];
assert.ok(acceptable.includes(output.trim()), `Unexpected output: ${JSON.stringify(output.trim())}`);
});