fix: Linux CI sandbox prereqs, platform-aware tests, broader prompt stripping

- Add bubblewrap and socat to Linux CI apt-get install
- Make sandbox test assertions platform-aware (macFileSystem vs linuxFileSystem)
- Make /etc/shells test accept both macOS and Linux first-line format
- Broaden wrapped prompt fragment regex to handle path chars (ts/testWorkspace$)
- Fix continuation pattern to match user@host:path wrapped lines
- Apply stripCommandEchoAndPrompt to getOutput() in BasicExecuteStrategy
  (basic shell integration lacks reliable 133;C markers so getOutput()
  can include command echo)
- Keep RichExecuteStrategy getOutput() unstripped (rich integration
  has reliable markers)
This commit is contained in:
Alex Dima
2026-03-21 19:06:50 +01:00
parent 5c733b67ac
commit 865568dbbc
5 changed files with 36 additions and 10 deletions

View File

@@ -42,7 +42,9 @@ jobs:
libxkbfile-dev \
libkrb5-dev \
libgbm1 \
rpm
rpm \
bubblewrap \
socat
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
sudo chmod +x /etc/init.d/xvfb
sudo update-rc.d xvfb defaults

View File

@@ -9,6 +9,10 @@ import * as vscode from 'vscode';
import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils';
const isWindows = process.platform === 'win32';
const isMacOS = process.platform === 'darwin';
const sandboxFileSystemSetting = isMacOS
? 'chat.tools.terminal.sandbox.macFileSystem'
: 'chat.tools.terminal.sandbox.linuxFileSystem';
/**
* Extracts all text content from a LanguageModelToolResult.
@@ -262,7 +266,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
// output analyzer prepends its error message.
assert.strictEqual(output.trim(), [
'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 chat.tools.terminal.sandbox.macFileSystem, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.',
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.`,
'- Otherwise, immediately retry the command with requestUnsandboxedExecution=true. Do NOT ask the user \u2014 setting this flag automatically shows a confirmation prompt to the user.',
'',
'Here is the output of the command:',
@@ -282,7 +286,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
assert.strictEqual(output.trim(), [
'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 chat.tools.terminal.sandbox.macFileSystem, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.',
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.`,
'- Otherwise, immediately retry the command with requestUnsandboxedExecution=true. Do NOT ask the user \u2014 setting this flag automatically shows a confirmation prompt to the user.',
'',
'Here is the output of the command:',
@@ -299,7 +303,13 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
const output = await invokeRunInTerminal('head -1 /etc/shells');
assert.strictEqual(output.trim(), '# List of acceptable shells for chpass(1).');
const trimmed = output.trim();
// macOS: "# List of acceptable shells for chpass(1)."
// Linux: "# /etc/shells: valid login shells"
assert.ok(
trimmed.startsWith('#'),
`Expected a comment line from /etc/shells, got: ${trimmed}`
);
});
test('can write inside the workspace folder', async function () {

View File

@@ -168,7 +168,7 @@ export class BasicExecuteStrategy extends Disposable implements ITerminalExecute
const commandOutput = finishedCommand?.getOutput();
if (commandOutput !== undefined) {
this._log('Fetched output via finished command');
output = commandOutput;
output = stripCommandEchoAndPrompt(commandOutput, commandLine);
}
}
if (output === undefined) {

View File

@@ -150,16 +150,16 @@ export function stripCommandEchoAndPrompt(output: string, commandLine: string):
// Prompt without @: hostname:path user$ or hostname:path user#
// e.g., "dsm12-be220-abc:testWorkspace runner$"
/^\s*[\w.-]+:\S.*\s\w+[#$]\s*$/.test(line) ||
// Wrapped prompt fragment: short word ending with $ or # (e.g. "er$", "ner$")
// Wrapped prompt fragment ending with $ or # (e.g. "er$", "ts/testWorkspace$")
// These appear when a prompt wraps across terminal columns.
/^\s*\w+[#$]\s*$/.test(line) ||
/^\s*[\w/.-]+[#$]\s*$/.test(line) ||
// Bracketed prompt start: [ user@host:/path (wrapped prompt first line)
// e.g., "[ alex@MacBook-Pro:/Users/alex/src/vscode4/extensions/vscode-api-test"
/^\[\s*\w+@[\w.-]+:/.test(line) ||
// Wrapped prompt continuation: hostname:path or hostname:path user (no trailing $)
// Wrapped prompt continuation: user@host:path or hostname:path (no trailing $)
// Only matched after we've already stripped a prompt fragment below.
// e.g., "dsm12-be220-abc:testWorkspace runn" (the "er$" was on the next line)
(trailingStrippedCount > 0 && /^\s*[\w][-\w.]*:\S/.test(line)) ||
// e.g., "cloudtest@host:/mnt/vss/.../vscode-api-tes" or "dsm12-abc:testWorkspace runn"
(trailingStrippedCount > 0 && /^\s*[\w][-\w.]*(@[\w.-]+)?:\S/.test(line)) ||
// Bracketed prompt end: ...] $ or ...] #
// e.g., "s/testWorkspace (main**) ] $ "
/\]\s*[#$]\s*$/.test(line) ||

View File

@@ -330,6 +330,20 @@ suite('stripCommandEchoAndPrompt', () => {
);
});
test('strips wrapped trailing prompt with path-like fragment (ts/testWorkspace$)', () => {
const output = [
'user@host:~ $ echo hello',
'hello',
'cloudtest@d4b0d881c000000:/mnt/vss/_work/vscode/vscode/extensions/vscode-api-tes',
'ts/testWorkspace$',
].join('\n');
assert.strictEqual(
stripCommandEchoAndPrompt(output, 'echo hello'),
'hello'
);
});
test('strips trailing prompt fragment for no-output command', () => {
const output = [
'dsm12-be220-abc:testWorkspace runner$ true',