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
28 changes: 0 additions & 28 deletions .vscode/sessions.json

This file was deleted.

13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,17 @@
"azureMcp.serverMode": "all",
"azureMcp.readOnly": true,
"debug.breakpointsView.presentation": "tree",
// --- Agent Sessions ---
"agentSessions.runScripts": [
{
"name": "Run",
"command": "./scripts/code.sh",
"commandWindows": ".\\scripts\\code.bat"
},
{
"name": "Tests",
"command": "./scripts/test.sh",
"commandWindows": ".\\scripts\\test.bat"
}
],
}
53 changes: 42 additions & 11 deletions build/azure-pipelines/common/sanity-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ parameters:
- name: baseImage
type: string
default: ""
- name: pageSize
type: string
default: ""
- name: args
type: string
default: ""
Expand All @@ -43,11 +40,46 @@ jobs:
sparseCheckoutDirectories: test/sanity .nvmrc
displayName: Checkout test/sanity

- task: NodeTool@0
inputs:
versionSource: fromFile
versionFilePath: .nvmrc
displayName: Install Node.js
- ${{ if and(eq(parameters.os, 'windows'), eq(parameters.arch, 'arm64')) }}:
- script: |
@echo off
setlocal enabledelayedexpansion

set "NODE_VERSION=v22.22.0"
set "EXPECTED_HASH=5b44fd410df7b4cd0a1891a05a7b606f8fb7d8786a94997b996a372e82478d7a"
set "NODE_ROOT=$(Agent.TempDirectory)\nodejs"
set "NODE_EXE=!NODE_ROOT!\node.exe"

if not exist "!NODE_EXE!" (
if exist "!NODE_ROOT!" rmdir /s /q "!NODE_ROOT!"

set "NODE_ZIP=$(Agent.TempDirectory)\node.zip"
curl.exe -fsSL "https://nodejs.org/dist/!NODE_VERSION!/node-!NODE_VERSION!-win-arm64.zip" -o "!NODE_ZIP!"

set "ACTUAL_HASH="
for /f "skip=1" %%A in ('certutil -hashfile "!NODE_ZIP!" SHA256') do if not defined ACTUAL_HASH set "ACTUAL_HASH=%%A"
if /I not "!ACTUAL_HASH!"=="!EXPECTED_HASH!" (
echo Hash mismatch for node.zip
echo expected: !EXPECTED_HASH!
echo actual: !ACTUAL_HASH!
del "!NODE_ZIP!"
exit /b 1
)

tar -xf "!NODE_ZIP!" -C "$(Agent.TempDirectory)"
ren "$(Agent.TempDirectory)\node-!NODE_VERSION!-win-arm64" nodejs
del "!NODE_ZIP!"
)

echo ##vso[task.prependpath]%NODE_ROOT%
displayName: Install Node.js (Windows ARM64)

- ${{ else }}:
- task: NodeTool@0
inputs:
versionSource: fromFile
versionFilePath: .nvmrc
displayName: Install Node.js

- script: npm config set registry "$(NPM_REGISTRY)" --location=project
workingDirectory: $(TEST_DIR)
Expand Down Expand Up @@ -88,9 +120,9 @@ jobs:
- ${{ if ne(parameters.container, '') }}:
- task: Cache@2
inputs:
key: 'docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "${{ parameters.pageSize }}" | "$(Agent.OS)" | $(TEST_DIR)/containers/${{ parameters.container }}.dockerfile'
key: 'docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "$(Agent.OS)" | $(TEST_DIR)/containers/${{ parameters.container }}.dockerfile'
path: $(DOCKER_CACHE_DIR)
restoreKeys: docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "${{ parameters.pageSize }}" | "$(Agent.OS)"
restoreKeys: docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "$(Agent.OS)"
cacheHitVar: DOCKER_CACHE_HIT
displayName: Download Docker Image

Expand All @@ -105,7 +137,6 @@ jobs:
--container "${{ parameters.container }}" \
--arch "${{ parameters.arch }}" \
--base-image "${{ parameters.baseImage }}" \
--page-size "${{ parameters.pageSize }}" \
--quality "$(BUILD_QUALITY)" \
--commit "$(BUILD_COMMIT)" \
--test-results "/root/results.xml" \
Expand Down
12 changes: 1 addition & 11 deletions build/azure-pipelines/product-sanity-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ extends:
displayName: Windows arm64
poolName: 1es-windows-2022-arm64
os: windows
arch: arm64

# Alpine 3.22
- template: build/azure-pipelines/common/sanity-tests.yml@self
Expand Down Expand Up @@ -332,14 +333,3 @@ extends:
container: ubuntu
baseImage: ubuntu:24.04
arch: arm64

- template: build/azure-pipelines/common/sanity-tests.yml@self
parameters:
name: ubuntu_24_04_arm64_64k
displayName: Ubuntu 24.04 arm64 (64K page)
poolName: 1es-ubuntu-22.04-x64
container: ubuntu
baseImage: ubuntu:24.04
arch: arm64
pageSize: 64k
args: --grep "desktop-linux-arm64"
14 changes: 0 additions & 14 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,20 +837,6 @@ export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePositi
};
}

/**
* Returns whether the element is in the bottom right quarter of the container.
*
* @param element the element to check for being in the bottom right quarter
* @param container the container to check against
* @returns true if the element is in the bottom right quarter of the container
*/
export function isElementInBottomRightQuarter(element: HTMLElement, container: HTMLElement): boolean {
const position = getDomNodePagePosition(element);
const clientArea = getClientArea(container);

return position.left > clientArea.width / 2 && position.top > clientArea.height / 2;
}

/**
* Returns the effective zoom on a given element before window zoom level is applied
*/
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class MenuId {
static readonly PanelAlignmentMenu = new MenuId('PanelAlignmentMenu');
static readonly PanelPositionMenu = new MenuId('PanelPositionMenu');
static readonly ActivityBarPositionMenu = new MenuId('ActivityBarPositionMenu');
static readonly NotificationsCenterPositionMenu = new MenuId('NotificationsCenterPositionMenu');
static readonly MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu');
static readonly MenubarRecentMenu = new MenuId('MenubarRecentMenu');
static readonly MenubarSelectionMenu = new MenuId('MenubarSelectionMenu');
Expand Down
133 changes: 93 additions & 40 deletions src/vs/sessions/contrib/chat/browser/runScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { equals } from '../../../../base/common/arrays.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { autorun, derived, IObservable } from '../../../../base/common/observable.js';
import { autorun, derivedOpts, IObservable } from '../../../../base/common/observable.js';
import { isMacintosh, isWindows } from '../../../../base/common/platform.js';
import { URI } from '../../../../base/common/uri.js';
import { localize, localize2 } from '../../../../nls.js';
import { MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js';
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
import { IActiveSessionItem, ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
import { ITerminalInstance, ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js';
import { Menus } from '../../../browser/menus.js';
import { ISessionsConfigurationService, ISessionScript } from './sessionsConfigurationService.js';
import { ISessionsConfigurationService, ISessionScript, ScriptStorageTarget } from './sessionsConfigurationService.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
import { isEqual } from '../../../../base/common/resources.js';



Expand Down Expand Up @@ -55,7 +58,16 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
) {
super();

this._activeRunState = derived(this, reader => {
this._activeRunState = derivedOpts<IRunScriptActionContext | undefined>({
owner: this,
equalsFn: (a, b) => {
if (a === b) { return true; }
if (!a || !b) { return false; }
return a.session === b.session
&& isEqual(a.cwd, b.cwd)
&& equals(a.scripts, b.scripts, (s1, s2) => s1.name === s2.name && s1.command === s2.command);
}
}, reader => {
const activeSession = this._activeSessionService.activeSession.read(reader);
const cwd = activeSession?.worktree ?? activeSession?.repository;
if (!activeSession || !cwd) {
Expand All @@ -64,7 +76,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr

const scripts = this._sessionsConfigService.getScripts(activeSession).read(reader);
return { session: activeSession, scripts, cwd };
});
}).recomputeInitiallyAndOnChange(this._store);

this._register(this._terminalService.onDidDisposeInstance(instance => {
for (const [key, id] of this._scriptTerminals) {
Expand All @@ -91,31 +103,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
const configureScriptPrecondition = session.worktree ? ContextKeyExpr.true() : ContextKeyExpr.false();
const addRunActionDisabledTooltip = session.worktree ? undefined : localize('configureScriptTooltipDisabled', "Actions can not be added in empty sessions");

if (scripts.length === 0) {
// No scripts configured - show a "Run Action" button that opens the configure quick pick
reader.store.add(registerAction2(class extends Action2 {
constructor() {
super({
id: RUN_SCRIPT_ACTION_ID,
title: localize('runScriptNoAction', "Run Action..."),
tooltip: localize('runScriptTooltipNoAction', "Configure action"),
icon: Codicon.play,
category: localize2('agentSessions', 'Agent Sessions'),
precondition: configureScriptPrecondition,
menu: [{
id: RunScriptDropdownMenuId,
when: configureScriptPrecondition,
group: 'navigation',
order: 0,
}]
});
}

async run(): Promise<void> {
await that._showConfigureQuickPick(session, cwd);
}
}));
} else {
if (scripts.length > 0) {
// Register an action for each script
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
Expand Down Expand Up @@ -149,14 +137,14 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
constructor() {
super({
id: CONFIGURE_DEFAULT_RUN_ACTION_ID,
title: localize2('configureDefaultRunAction', "Add Action..."),
title: localize2('configureDefaultRunAction', "Add Run Action..."),
tooltip: addRunActionDisabledTooltip,
category: localize2('agentSessions', 'Agent Sessions'),
icon: Codicon.play,
precondition: configureScriptPrecondition,
menu: [{
id: RunScriptDropdownMenuId,
group: '1_configure',
group: scripts.length === 0 ? 'navigation' : '1_configure',
order: 0
}]
});
Expand All @@ -175,15 +163,67 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
prompt: localize('enterCommandPrompt', "This command will be run in the integrated terminal")
});

if (command) {
const script: ISessionScript = { name: command, command };
await this._sessionsConfigService.addScript(script, session);
await this._runScript(cwd, script);
if (!command) {
return;
}

const target = await this._pickStorageTarget(session);
if (!target) {
return;
}

const script: ISessionScript = { name: command, command };
await this._sessionsConfigService.addScript(script, session, target);
await this._runScript(cwd, script);
}

private async _pickStorageTarget(session: IActiveSessionItem): Promise<ScriptStorageTarget | undefined> {
const hasWorktree = !!session.worktree;

interface IStorageTargetItem extends IQuickPickItem {
target: ScriptStorageTarget;
}

const items: IStorageTargetItem[] = [
{
target: 'user',
label: localize('storeInUserSettings', "User Settings"),
description: localize('storeInUserSettingsDesc', "Available in all sessions"),
},
{
target: 'workspace',
label: localize('storeInWorkspaceSettings', "Workspace Settings"),
description: hasWorktree
? localize('storeInWorkspaceSettingsDesc', "Stored in session worktree")
: localize('storeInWorkspaceSettingsDisabled', "Not available in empty sessions"),
italic: !hasWorktree,
disabled: !hasWorktree,
},
];

return new Promise<ScriptStorageTarget | undefined>(resolve => {
const picker = this._quickInputService.createQuickPick<IStorageTargetItem>({ useSeparators: true });
picker.placeholder = localize('pickStorageTarget', "Where should this action be saved?");
picker.items = items;

picker.onDidAccept(() => {
const selected = picker.activeItems[0];
if (selected && (selected.target !== 'workspace' || hasWorktree)) {
picker.dispose();
resolve(selected.target);
}
});
picker.onDidHide(() => {
picker.dispose();
resolve(undefined);
});
picker.show();
});
}

private async _runScript(cwd: URI, script: ISessionScript): Promise<void> {
const key = this._terminalKey(cwd, script);
const command = this._resolveCommand(script);
const key = this._terminalKey(cwd, command);
let terminal = this._getReusableTerminal(key);

if (!terminal) {
Expand All @@ -197,13 +237,26 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
this._scriptTerminals.set(key, terminal.instanceId);
}

await terminal.sendText(script.command, true);
await terminal.sendText(command, true);
this._terminalService.setActiveInstance(terminal);
await this._terminalService.revealActiveTerminal();
}

private _terminalKey(cwd: URI, script: ISessionScript): string {
return `${cwd.toString()}\n${script.command}`;
private _resolveCommand(script: ISessionScript): string {
if (isWindows && script.commandWindows) {
return script.commandWindows;
}
if (isMacintosh && script.commandMacOS) {
return script.commandMacOS;
}
if (!isWindows && !isMacintosh && script.commandLinux) {
return script.commandLinux;
}
return script.command;
}

private _terminalKey(cwd: URI, command: string): string {
return `${cwd.toString()}\n${command}`;
}

private _getReusableTerminal(key: string): ITerminalInstance | undefined {
Expand Down
Loading
Loading