Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"install-latest-component-explorer": "npm install @vscode/component-explorer@next @vscode/component-explorer-cli@next && cd build/vite && npm install @vscode/component-explorer-vite-plugin@next && npm install @vscode/component-explorer@next"
},
"dependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.23",
"@anthropic-ai/sandbox-runtime": "0.0.42",
"@github/copilot": "^1.0.4-0",
"@github/copilot-sdk": "^0.1.32",
"@microsoft/1ds-core-js": "^3.2.13",
Expand Down
10 changes: 5 additions & 5 deletions remote/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.23",
"@anthropic-ai/sandbox-runtime": "0.0.42",
"@github/copilot": "^1.0.4-0",
"@github/copilot-sdk": "^0.1.32",
"@microsoft/1ds-core-js": "^3.2.13",
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHostWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
disregardSearchExcludeSettings: options.useExcludeSettings !== undefined && (options.useExcludeSettings !== ExcludeSettingOptions.SearchAndFilesExclude),
maxResults: options.maxResults,
excludePattern: excludePatterns.length > 0 ? excludePatterns : undefined,
ignoreGlobCase: options.caseInsensitive,
_reason: 'startFileSearch',
shouldGlobSearch: query.type === 'include' ? undefined : true,
};
Expand Down Expand Up @@ -597,6 +598,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
disregardSearchExcludeSettings: options.useExcludeSettings !== undefined && (options.useExcludeSettings !== ExcludeSettingOptions.SearchAndFilesExclude),
fileEncoding: options.encoding,
maxResults: options.maxResults,
ignoreGlobCase: options.caseInsensitive,
previewOptions: options.previewOptions ? {
matchLines: options.previewOptions?.numMatchLines ?? 100,
charsPerLine: options.previewOptions?.charsPerLine ?? 10000,
Expand Down
37 changes: 37 additions & 0 deletions src/vs/workbench/api/test/browser/extHostWorkspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,25 @@ suite('ExtHostWorkspace', function () {
});
});

test('caseInsensitive', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();

let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.ignoreGlobCase, true);
return Promise.resolve(null);
}
});

const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2([''], { caseInsensitive: true }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});

// todo: add tests with multiple filePatterns and excludes

});
Expand Down Expand Up @@ -1096,6 +1115,24 @@ suite('ExtHostWorkspace', function () {
assert(mainThreadCalled, 'mainThreadCalled');
});

test('caseInsensitive', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();

let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
assert.strictEqual(options.ignoreGlobCase, true);
return null;
}
});

const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
await (ws.findTextInFiles2({ pattern: 'foo' }, { caseInsensitive: true }, new ExtensionIdentifier('test'))).complete;
assert(mainThreadCalled, 'mainThreadCalled');
});

// TODO: test multiple includes/excludess
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { h } from '../../../../../../../base/browser/dom.js';
import { renderLabelWithIcons } from '../../../../../../../base/browser/ui/iconLabel/iconLabels.js';
import { ActionBar } from '../../../../../../../base/browser/ui/actionbar/actionbar.js';
import { isMarkdownString, MarkdownString } from '../../../../../../../base/common/htmlContent.js';
import { escapeMarkdownSyntaxTokens, isMarkdownString, MarkdownString } from '../../../../../../../base/common/htmlContent.js';
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js';
import { ChatConfiguration } from '../../../../common/constants.js';
Expand Down Expand Up @@ -1641,11 +1640,11 @@ class ChatTerminalThinkingCollapsibleWrapper extends ChatCollapsibleContentPart
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
) {
const title = isComplete ? `Ran \`${commandText}\`` : `Running \`${commandText}\``;
const title = isComplete ? `Ran \`${escapeMarkdownSyntaxTokens(commandText)}\`` : `Running \`${escapeMarkdownSyntaxTokens(commandText)}\``;
super(title, context, undefined, hoverService, configurationService);

this._terminalContentElement = contentElement;
this._commandText = commandText;
this._commandText = escapeMarkdownSyntaxTokens(commandText);
this._isSandboxWrapped = isSandboxWrapped;
this._isComplete = isComplete;

Expand All @@ -1664,15 +1663,16 @@ class ChatTerminalThinkingCollapsibleWrapper extends ChatCollapsibleContentPart
return;
}

const labelElement = this._collapseButton.labelElement;
labelElement.textContent = '';
if (this._isSandboxWrapped) {
dom.reset(labelElement, ...renderLabelWithIcons(this._isComplete
this._collapseButton.label = new MarkdownString(this._isComplete
? '$(lock) ' + localize('chat.terminal.ranInSandbox', "Ran `{0}` in sandbox", this._commandText)
: '$(lock) ' + localize('chat.terminal.runningInSandbox', "Running `{0}` in sandbox", this._commandText)));
: '$(lock) ' + localize('chat.terminal.runningInSandbox', "Running `{0}` in sandbox", this._commandText), { supportThemeIcons: true });
return;
}

const labelElement = this._collapseButton.labelElement;
labelElement.textContent = '';

const prefixText = this._isComplete
? localize('chat.terminal.ran.prefix', "Ran ")
: localize('chat.terminal.running.prefix', "Running ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Event } from '../../../../../../base/common/event.js';
import { MutableDisposable, toDisposable, DisposableStore } from '../../../../../../base/common/lifecycle.js';
import { MarshalledId } from '../../../../../../base/common/marshallingIds.js';
import { autorun, IReader } from '../../../../../../base/common/observable.js';
import { isEqual } from '../../../../../../base/common/resources.js';
import { URI } from '../../../../../../base/common/uri.js';
import { localize } from '../../../../../../nls.js';
import { MenuWorkbenchToolBar } from '../../../../../../platform/actions/browser/toolbar.js';
Expand Down Expand Up @@ -628,6 +629,16 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
}
}));

// When the currently displayed session is archived, start a new session
this._register(this.agentSessionsService.model.onDidChangeSessionArchivedState(e => {
if (e.isArchived()) {
const currentSessionResource = chatWidget.viewModel?.sessionResource;
if (currentSessionResource && isEqual(currentSessionResource, e.resource)) {
this.clear();
}
}
}));

// When showing sessions stacked, adjust the height of the sessions list to make room for chat input
this._register(autorun(reader => {
chatWidget.inputPart.height.read(reader);
Expand Down
12 changes: 6 additions & 6 deletions src/vs/workbench/contrib/chat/common/model/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,9 @@ export class Response extends AbstractResponse implements IDisposable {
);

if (existingInvocation) {
if (progress.toolSpecificData !== undefined) {
existingInvocation.toolSpecificData = progress.toolSpecificData;
}
if (progress.isComplete) {
existingInvocation.didExecuteTool({
content: [],
Expand All @@ -870,9 +873,6 @@ export class Response extends AbstractResponse implements IDisposable {
toolResultDetails: progress.resultDetails
});
}
if (progress.toolSpecificData !== undefined) {
existingInvocation.toolSpecificData = progress.toolSpecificData;
}
return;
}

Expand Down Expand Up @@ -900,15 +900,15 @@ export class Response extends AbstractResponse implements IDisposable {

if (progress.isComplete) {
// Already completed on first push
if (progress.toolSpecificData !== undefined) {
invocation.toolSpecificData = progress.toolSpecificData;
}
invocation.didExecuteTool({
content: [],
toolResultMessage: progress.pastTenseMessage,
toolResultError: progress.errorMessage,
toolResultDetails: progress.resultDetails
});
if (progress.toolSpecificData !== undefined) {
invocation.toolSpecificData = progress.toolSpecificData;
}
}

this._responseParts.push(invocation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { IChatRequestImplicitVariableEntry, IChatRequestStringVariableEntry, ICh
import { ChatAgentService, IChatAgentService } from '../../../common/participants/chatAgents.js';
import { ChatModel, ChatRequestModel, ChatResponseResource, IChatRequestModeInfo, IExportableChatData, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, isExportableSessionData, isSerializableSessionData, normalizeSerializableChatData, Response } from '../../../common/model/chatModel.js';
import { ChatRequestTextPart } from '../../../common/requestParser/chatParserTypes.js';
import { ChatRequestQueueKind, IChatService, IChatToolInvocation } from '../../../common/chatService/chatService.js';
import { ChatRequestQueueKind, IChatService, IChatTerminalToolInvocationData, IChatToolInvocation } from '../../../common/chatService/chatService.js';
import { ChatAgentLocation, ChatModeKind } from '../../../common/constants.js';
import { MockChatService } from '../chatService/mockChatService.js';

Expand Down Expand Up @@ -553,6 +553,65 @@ suite('Response', () => {
assert.strictEqual(textEditGroups.length, 0, 'Should not have textEditGroup for cell edits');
assert.strictEqual(notebookEditGroups.length, 1, 'Should have notebookEditGroup for cell edits');
});

test('external terminal tool updates preserve toolSpecificData when completing an existing invocation', () => {
const response = store.add(new Response([]));
const toolSpecificData: IChatTerminalToolInvocationData = {
kind: 'terminal',
language: 'bash',
commandLine: { original: 'npm test' },
terminalCommandOutput: { text: 'all green' },
terminalCommandState: { exitCode: 0 },
};

response.updateContent({
kind: 'externalToolInvocationUpdate',
toolCallId: 'tool-call-1',
toolName: 'run_in_terminal',
isComplete: false,
invocationMessage: 'Running npm test',
});

response.updateContent({
kind: 'externalToolInvocationUpdate',
toolCallId: 'tool-call-1',
toolName: 'run_in_terminal',
isComplete: true,
pastTenseMessage: 'Ran npm test',
toolSpecificData,
});

assert.strictEqual(response.value.length, 1);
assert.strictEqual(response.value[0].kind, 'toolInvocation');
assert.deepStrictEqual(response.value[0].toolSpecificData, toolSpecificData);
assert.strictEqual(IChatToolInvocation.isComplete(response.value[0]), true);
});

test('external terminal tool updates preserve toolSpecificData when first pushed as complete', () => {
const response = store.add(new Response([]));
const toolSpecificData: IChatTerminalToolInvocationData = {
kind: 'terminal',
language: 'bash',
commandLine: { original: 'npm test' },
terminalCommandOutput: { text: 'all green' },
terminalCommandState: { exitCode: 0 },
};

response.updateContent({
kind: 'externalToolInvocationUpdate',
toolCallId: 'tool-call-2',
toolName: 'run_in_terminal',
isComplete: true,
invocationMessage: 'Running npm test',
pastTenseMessage: 'Ran npm test',
toolSpecificData,
});

assert.strictEqual(response.value.length, 1);
assert.strictEqual(response.value[0].kind, 'toolInvocation');
assert.deepStrictEqual(response.value[0].toolSpecificData, toolSpecificData);
assert.strictEqual(IChatToolInvocation.isComplete(response.value[0]), true);
});
});

suite('normalizeSerializableChatData', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ Background Processes:
parts.push(`
Sandboxing:
- ATTENTION: Terminal sandboxing is enabled, commands run in a sandbox by default
- When executing commands within the sandboxed environment, all operations requiring a temporary directory must utilize the $TMPDIR environment variable. The /tmp directory is not guaranteed to be accessible or writable and must be avoided
- Tools and scripts should respect the TMPDIR environment variable, which is automatically set to an appropriate path within the sandbox
- When a command fails due to sandbox restrictions, immediately re-run it with requestUnsandboxedExecution=true and prompt the user to bypass the sandbox
- Only set requestUnsandboxedExecution=true when there is evidence of failures caused by the sandbox, e.g. 'Operation not permitted' errors, network failures, or file access errors, etc
- When setting requestUnsandboxedExecution=true, also provide requestUnsandboxedExecutionReason; the user will be prompted before it runs unsandboxed`);
Expand Down Expand Up @@ -1006,12 +1008,23 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
? `Note: The tool simplified the command to \`${command}\`, and that command is now running in terminal with ID=${termId}`
: `Command is running in terminal with ID=${termId}`
);
const backgroundOutput = pollingResult?.modelOutputEvalResponse ?? pollingResult?.output;
const outputAnalyzerMessage = backgroundOutput
? await this._getOutputAnalyzerMessage(undefined, backgroundOutput, command, didSandboxWrapCommand)
: undefined;
if (pollingResult && pollingResult.modelOutputEvalResponse) {
resultText += `\n\ The command became idle with output:\n${pollingResult.modelOutputEvalResponse}`;
resultText += `\n\ The command became idle with output:\n`;
if (outputAnalyzerMessage) {
resultText += `${outputAnalyzerMessage}\n`;
}
resultText += pollingResult.modelOutputEvalResponse;
} else if (pollingResult) {
resultText += `\n\ The command is still running, with output:\n${pollingResult.output}`;
resultText += `\n\ The command is still running, with output:\n`;
if (outputAnalyzerMessage) {
resultText += `${outputAnalyzerMessage}\n`;
}
resultText += pollingResult.output;
}

const endCwd = await toolTerminal.instance.getCwdResource();
return {
toolMetadata: {
Expand Down Expand Up @@ -1206,14 +1219,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
if (didTimeout && timeoutValue !== undefined && timeoutValue > 0) {
resultText.push(`Note: Command timed out after ${timeoutValue}ms. Output collected so far is shown below and the command may still be running in terminal ID ${termId}.\n\n`);
}
let outputAnalyzerMessage: string | undefined;
for (const analyzer of this._outputAnalyzers) {
const message = await analyzer.analyze({ exitCode, exitResult: terminalResult, commandLine: command, isSandboxWrapped: didSandboxWrapCommand });
if (message) {
outputAnalyzerMessage = message;
break;
}
}
const outputAnalyzerMessage = await this._getOutputAnalyzerMessage(exitCode, terminalResult, command, didSandboxWrapCommand);
if (outputAnalyzerMessage) {
resultText.push(`${outputAnalyzerMessage}\n`);
}
Expand Down Expand Up @@ -1248,6 +1254,17 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
};
}

private async _getOutputAnalyzerMessage(exitCode: number | undefined, exitResult: string, commandLine: string, isSandboxWrapped: boolean): Promise<string | undefined> {
for (const analyzer of this._outputAnalyzers) {
const message = await analyzer.analyze({ exitCode, exitResult, commandLine, isSandboxWrapped });
if (message) {
return message;
}
}

return undefined;
}

private static readonly _maxImageFileSize = 5 * 1024 * 1024;

/**
Expand Down
Loading
Loading