Merge pull request #300818 from microsoft/ben/fancy-muskox

E2E sessions test updates
This commit is contained in:
Osvaldo Ortega
2026-03-12 16:26:10 -07:00
committed by GitHub
12 changed files with 484 additions and 16 deletions

View File

@@ -1965,6 +1965,7 @@ export default tseslint.config(
'vs/editor/contrib/*/~',
'vs/editor/editor.all.js',
'vs/sessions/~',
'vs/sessions/services/*/~',
'vs/sessions/contrib/*/~',
'vs/workbench/~',
'vs/workbench/api/~',
@@ -1984,6 +1985,7 @@ export default tseslint.config(
'vs/editor/contrib/*/~',
'vs/editor/editor.all.js',
'vs/sessions/~',
'vs/sessions/services/*/~',
'vs/sessions/contrib/*/~',
'vs/workbench/~',
'vs/workbench/api/~',
@@ -2083,6 +2085,7 @@ export default tseslint.config(
'vs/editor/contrib/*/~',
'vs/workbench/~',
'vs/workbench/services/*/~',
'vs/sessions/~',
'vs/sessions/services/*/~',
{
'when': 'test',

View File

@@ -99,9 +99,14 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben
let existing = await this._findTerminalsForKey(key);
if (existing.length === 0) {
existing = [await this._terminalService.createTerminal({ config: { cwd } })];
this._terminalService.setActiveInstance(existing[0]);
this._logService.trace(`[SessionsTerminal] Created terminal ${existing[0].instanceId} for ${cwd.fsPath}`);
try {
existing = [await this._terminalService.createTerminal({ config: { cwd } })];
this._terminalService.setActiveInstance(existing[0]);
this._logService.trace(`[SessionsTerminal] Created terminal ${existing[0].instanceId} for ${cwd.fsPath}`);
} catch (e) {
this._logService.trace(`[SessionsTerminal] Cannot create terminal for ${cwd.fsPath}: ${e}`);
return [];
}
}
if (focus) {

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { ITitleService } from '../../../../workbench/services/title/browser/titleService.js';
import { TitleService } from '../../../browser/parts/titlebarPart.js';
registerSingleton(ITitleService, TitleService, InstantiationType.Eager);

View File

@@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InstantiationType, registerSingleton } from '../../platform/instantiation/common/extensions.js';
import { ITitleService } from '../../workbench/services/title/browser/titleService.js';
import { NativeTitleService } from './parts/titlebarPart.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { ITitleService } from '../../../../workbench/services/title/browser/titleService.js';
import { NativeTitleService } from '../../../electron-browser/parts/titlebarPart.js';
registerSingleton(ITitleService, NativeTitleService, InstantiationType.Eager);

View File

@@ -42,7 +42,7 @@ import '../workbench/services/update/electron-browser/updateService.js';
import '../workbench/services/url/electron-browser/urlService.js';
import '../workbench/services/lifecycle/electron-browser/lifecycleService.js';
import '../workbench/services/host/electron-browser/nativeHostService.js';
import './electron-browser/titleService.js';
import './services/title/electron-browser/titleService.js';
import '../platform/meteredConnection/electron-browser/meteredConnectionService.js';
import '../workbench/services/request/electron-browser/requestService.js';
import '../workbench/services/clipboard/electron-browser/clipboardService.js';

View File

@@ -90,8 +90,7 @@ import { UserDataAutoSyncService } from '../platform/userDataSync/common/userDat
import { AccessibilityService } from '../platform/accessibility/browser/accessibilityService.js';
import { ICustomEndpointTelemetryService } from '../platform/telemetry/common/telemetry.js';
import { NullEndpointTelemetryService } from '../platform/telemetry/common/telemetryUtils.js';
import { ITitleService } from '../workbench/services/title/browser/titleService.js';
import { BrowserTitleService } from '../workbench/browser/parts/titlebar/titlebarPart.js';
import './services/title/browser/titleService.js';
import { ITimerService, TimerService } from '../workbench/services/timer/browser/timerService.js';
import { IDiagnosticsService, NullDiagnosticsService } from '../platform/diagnostics/common/diagnostics.js';
import { ILanguagePackService } from '../platform/languagePacks/common/languagePacks.js';
@@ -111,7 +110,6 @@ registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, Insta
registerSingleton(IUserDataSyncService, UserDataSyncService, InstantiationType.Delayed);
registerSingleton(IUserDataSyncResourceProviderService, UserDataSyncResourceProviderService, InstantiationType.Delayed);
registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService, InstantiationType.Eager);
registerSingleton(ITitleService, BrowserTitleService, InstantiationType.Eager);
registerSingleton(IExtensionTipsService, ExtensionTipsService, InstantiationType.Delayed);
registerSingleton(ITimerService, TimerService, InstantiationType.Delayed);
registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService, InstantiationType.Delayed);
@@ -154,7 +152,9 @@ import './browser/layoutActions.js';
import './contrib/accountMenu/browser/account.contribution.js';
import './contrib/aiCustomizationTreeView/browser/aiCustomizationTreeView.contribution.js';
import './contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.js';
import './contrib/chat/browser/chat.contribution.js';
import './contrib/terminal/browser/sessionsTerminalContribution.js';
import './contrib/sessions/browser/sessions.contribution.js';
import './contrib/sessions/browser/customizationsToolbar.contribution.js';
import './contrib/changes/browser/changesView.contribution.js';

View File

@@ -21,6 +21,8 @@ runs through the real code paths.
| Chat agents (`copilotcli`, etc.) | Canned keyword-matched responses with `textEdit` progress items | No real LLM backend |
| `mock-fs://` FileSystemProvider | `InMemoryFileSystemProvider` registered directly in the workbench (not extension host) | Must be available before any service tries to resolve workspace files |
| GitHub authentication | Always-signed-in mock provider (extension) | No real OAuth flow |
| Code Review command | Returns canned review comments per file (extension) | No real Copilot AI review |
| PR commands (Create/Open/Merge) | No-op handlers that log and show info messages (extension) | No real GitHub API |
### What's Real (Everything Else)
@@ -39,6 +41,10 @@ exercise the actual code paths:
observations
- **Menu actions** — "Create PR", "Accept", "Reject" buttons appear based on
real context key state
- **`CodeReviewService`** — Orchestrates review requests, processes results from
the mock `github.copilot.chat.codeReview.run` command, and stores comments
- **`CodeReviewToolbarContribution`** — Shows the Code Review button in the
Changes view toolbar based on real context key state
### Data Flow
@@ -57,6 +63,26 @@ User types message → Chat Widget → ChatService
The mock agent is the **only** point where canned data enters the system.
Everything downstream uses real service implementations.
### Code Review & PR Button Flow
```
Code Review button clicked → sessions.codeReview.run (core action)
→ CodeReviewService.requestReview()
→ commandService.executeCommand('chat.internal.codeReview.run')
→ Bridge forwards to 'github.copilot.chat.codeReview.run'
→ Mock extension returns canned comments
→ CodeReviewService stores results, updates observable state
→ CodeReviewToolbarContribution updates button icon/badge
Create PR button clicked → github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR
→ Mock extension logs and shows info message
```
The PR buttons (Create PR, Open PR, Merge) are contributed via the mock
extension's `package.json` menus, gated by `chatSessionType == copilotcli`.
The `chatSessionType` context key is derived from the session URI scheme
(`getChatSessionType()`), which returns `copilotcli` for mock sessions.
### Why the FileSystem Provider Is Registered in the Workbench
The `mock-fs://` `InMemoryFileSystemProvider` is registered directly on

View File

@@ -10,7 +10,9 @@
/**
* Mock extension for Sessions E2E testing.
*
* Provides a fake GitHub authentication provider (skips sign-in).
* Provides:
* - A fake GitHub authentication provider (skips sign-in)
* - Mock command handlers for Code Review, Create PR, Open PR, and Merge
*
* The mock-fs:// FileSystemProvider and chat agents are registered
* directly in the workbench (web.test.ts), not here.
@@ -31,6 +33,9 @@ function activate(context) {
// 1. Mock GitHub Authentication Provider
context.subscriptions.push(registerMockAuth(vscode));
// 2. Mock command handlers for Code Review and PR actions
context.subscriptions.push(...registerMockCommands(vscode));
// Note: The mock-fs:// FileSystemProvider is registered directly in the
// workbench (web.test.ts → registerMockFileSystemProvider) so it is
// available before any service tries to resolve workspace files.
@@ -82,6 +87,88 @@ function registerMockAuth(vscode) {
});
}
// ---------------------------------------------------------------------------
// Mock Command Handlers (Code Review + PR Actions)
// ---------------------------------------------------------------------------
/**
* Registers mock command handlers that stand in for the real GitHub Copilot
* extension commands. These allow the Code Review and Create PR buttons to
* function in the e2e test environment.
*
* @param {typeof import('vscode')} vscode
* @returns {import('vscode').Disposable[]}
*/
function registerMockCommands(vscode) {
const disposables = [];
// Mock create PR — simulates successful PR creation
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR',
() => {
console.log('[sessions-e2e-mock] Mock Create PR invoked');
vscode.window.showInformationMessage('Mock: Pull request created successfully');
}
));
// Mock open PR — simulates opening a PR URL
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.openPullRequestCopilotCLIAgentSession.openPR',
() => {
console.log('[sessions-e2e-mock] Mock Open PR invoked');
vscode.window.showInformationMessage('Mock: Opening pull request');
}
));
// Mock merge — simulates merging changes
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.mergeCopilotCLIAgentSessionChanges.merge',
() => {
console.log('[sessions-e2e-mock] Mock Merge invoked');
vscode.window.showInformationMessage('Mock: Changes merged successfully');
}
));
// Mock merge and sync — simulates merging and syncing
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.mergeCopilotCLIAgentSessionChanges.mergeAndSync',
() => {
console.log('[sessions-e2e-mock] Mock Merge and Sync invoked');
vscode.window.showInformationMessage('Mock: Changes merged and synced successfully');
}
));
// Mock apply changes — simulates applying session changes
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.applyCopilotCLIAgentSessionChanges.apply',
() => {
console.log('[sessions-e2e-mock] Mock Apply Changes invoked');
vscode.window.showInformationMessage('Mock: Changes applied successfully');
}
));
// Mock checkout PR reroute — simulates checkout PR flow
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.checkoutPullRequestReroute',
() => {
console.log('[sessions-e2e-mock] Mock Checkout PR Reroute invoked');
vscode.window.showInformationMessage('Mock: Checking out pull request');
}
));
// Mock update changes — simulates updating session changes
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.updateCopilotCLIAgentSessionChanges.update',
() => {
console.log('[sessions-e2e-mock] Mock Update Changes invoked');
vscode.window.showInformationMessage('Mock: Changes updated successfully',);
}
));
console.log('[sessions-e2e-mock] Registered mock Code Review and PR command handlers');
return disposables;
}
// ---------------------------------------------------------------------------
// Exports
// ---------------------------------------------------------------------------

View File

@@ -10,6 +10,7 @@
"extensionKind": ["ui", "workspace"],
"browser": "./extension.js",
"activationEvents": ["*"],
"enabledApiProposals": ["chatSessionsProvider"],
"capabilities": {
"virtualWorkspaces": true,
"untrustedWorkspaces": {
@@ -22,6 +23,88 @@
"id": "github",
"label": "GitHub (Mock)"
}
]
],
"commands": [
{
"command": "github.copilot.chat.applyCopilotCLIAgentSessionChanges.apply",
"title": "Apply Changes",
"icon": "$(check)"
},
{
"command": "github.copilot.chat.checkoutPullRequestReroute",
"title": "Checkout Pull Request",
"icon": "$(git-pull-request)"
},
{
"command": "github.copilot.chat.openPullRequestCopilotCLIAgentSession.openPR",
"title": "Open Pull Request",
"icon": "$(link-external)"
},
{
"command": "github.copilot.chat.mergeCopilotCLIAgentSessionChanges.merge",
"title": "Merge",
"icon": "$(git-merge)"
},
{
"command": "github.copilot.chat.mergeCopilotCLIAgentSessionChanges.mergeAndSync",
"title": "Merge and Sync",
"icon": "$(git-merge)"
},
{
"command": "github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR",
"title": "Create Pull Request",
"icon": "$(git-pull-request-create)"
},
{
"command": "github.copilot.chat.updateCopilotCLIAgentSessionChanges.update",
"title": "Update",
"icon": "$(cloud-upload)"
},
{
"command": "github.copilot.chat.codeReview.run",
"title": "Run Code Review"
}
],
"menus": {
"chat/input/editing/sessionToolbar": [
{
"command": "github.copilot.chat.applyCopilotCLIAgentSessionChanges.apply",
"when": "chatSessionType == copilotcli && workbenchState != empty && !isSessionsWindow",
"group": "navigation@0"
},
{
"command": "github.copilot.chat.checkoutPullRequestReroute",
"when": "chatSessionType == copilot-cloud-agent && !github.vscode-pull-request-github.activated && gitOpenRepositoryCount != 0",
"group": "navigation@0"
},
{
"command": "github.copilot.chat.openPullRequestCopilotCLIAgentSession.openPR",
"when": "chatSessionType == copilotcli && isSessionsWindow && sessions.hasOpenPullRequest",
"group": "navigation@1"
}
],
"chat/input/editing/sessionApplyActions": [
{
"command": "github.copilot.chat.mergeCopilotCLIAgentSessionChanges.merge",
"when": "chatSessionType == copilotcli && isSessionsWindow && !sessions.isMergeBaseBranchProtected",
"group": "merge@1"
},
{
"command": "github.copilot.chat.mergeCopilotCLIAgentSessionChanges.mergeAndSync",
"when": "chatSessionType == copilotcli && isSessionsWindow && !sessions.isMergeBaseBranchProtected",
"group": "merge@2"
},
{
"command": "github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR",
"when": "chatSessionType == copilotcli && isSessionsWindow && sessions.isMergeBaseBranchProtected && !sessions.hasOpenPullRequest",
"group": "pull_request@1"
},
{
"command": "github.copilot.chat.updateCopilotCLIAgentSessionChanges.update",
"when": "chatSessionType == copilotcli && isSessionsWindow",
"group": "update@1"
}
]
}
}
}

View File

@@ -0,0 +1,25 @@
# Scenario: Full workflow
## Steps
1. Type "build the project" in the chat input
2. Press Enter to submit
3. Verify there is a response in the chat
4. Toggle the secondary side bar
5. Verify the changes view shows "CHANGES" with a badge
6. Verify "package.json" is visible in the changes list
7. Verify "build.ts" is visible in the changes list
8. Verify "index.ts" is visible in the changes list
9. Click on "index.ts" in the changes list
10. Verify a diff editor opens with modified content
11. Press Escape to close the diff editor
12. Verify "Merge" button is visible in changes view header
13. Verify the "Open Terminal" button is visible
14. Click the "Open Terminal" button
15. Verify the terminal panel becomes visible
16. Verify the terminal tab shows "session-1" in its label
17. Click "New Session" to create a new session
18. Type "fix the bug" in the chat input
19. Press Enter to submit
20. Verify the terminal tab label changes to show "session-2"
21. Click back on the first session in the sessions list
22. Verify the terminal tab label changes back to show "session-1"

View File

@@ -0,0 +1,140 @@
{
"scenario": "Scenario: Full workflow",
"generatedAt": "2026-03-12T22:48:50.725Z",
"steps": [
{
"description": "Type \"build the project\" in the chat input",
"commands": [
"click textbox \"Chat input\"",
"type \"build the project\""
]
},
{
"description": "Press Enter to submit",
"commands": [
"press Enter"
]
},
{
"description": "Verify there is a response in the chat",
"commands": [
"# ASSERT_VISIBLE: I'll help you build the project. Here are the changes:"
]
},
{
"description": "Toggle the secondary side bar",
"commands": [
"click button \"Toggle Secondary Side Bar Visibility\""
]
},
{
"description": "Verify the changes view shows \"CHANGES\" with a badge",
"commands": [
"# ASSERT_VISIBLE: Changes - 3 files changed"
]
},
{
"description": "Verify \"package.json\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: package.json"
]
},
{
"description": "Verify \"build.ts\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: build.ts"
]
},
{
"description": "Verify \"index.ts\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: index.ts"
]
},
{
"description": "Click on \"index.ts\" in the changes list",
"commands": [
"click treeitem \"index.ts\""
]
},
{
"description": "Verify a diff editor opens with modified content",
"commands": [
"# ASSERT_VISIBLE: index.ts"
]
},
{
"description": "Press Escape to close the diff editor",
"commands": [
"press Escape"
]
},
{
"description": "Verify \"Merge\" button is visible in changes view header",
"commands": [
"# ASSERT_VISIBLE: Merge"
]
},
{
"description": "Verify the \"Open Terminal\" button is visible",
"commands": [
"# ASSERT_VISIBLE: Open Terminal"
]
},
{
"description": "Click the \"Open Terminal\" button",
"commands": [
"click button \"Open Terminal\""
]
},
{
"description": "Verify the terminal panel becomes visible",
"commands": [
"# ASSERT_VISIBLE: Terminal"
]
},
{
"description": "Verify the terminal tab shows \"session-1\" in its label",
"commands": [
"# ASSERT_VISIBLE: bash - session-1"
]
},
{
"description": "Click \"New Session\" to create a new session",
"commands": [
"click button \"New Session\""
]
},
{
"description": "Type \"fix the bug\" in the chat input",
"commands": [
"click textbox \"Chat input\"",
"type \"fix the bug\""
]
},
{
"description": "Press Enter to submit",
"commands": [
"press Enter"
]
},
{
"description": "Verify the terminal tab label changes to show \"session-2\"",
"commands": [
"# ASSERT_VISIBLE: bash - session-2"
]
},
{
"description": "Click back on the first session in the sessions list",
"commands": [
"click listitem \"build the project\""
]
},
{
"description": "Verify the terminal tab label changes back to show \"session-1\"",
"commands": [
"# ASSERT_VISIBLE: bash - session-1"
]
}
]
}

View File

@@ -25,6 +25,10 @@ import { IChatProgress } from '../../workbench/contrib/chat/common/chatService/c
import { IChatSessionsService, IChatSessionItem, IChatSessionFileChange, ChatSessionStatus, IChatSessionHistoryItem, IChatSessionItemsDelta } from '../../workbench/contrib/chat/common/chatSessionsService.js';
import { IGitService, IGitExtensionDelegate, IGitRepository } from '../../workbench/contrib/git/common/gitService.js';
import { IFileService } from '../../platform/files/common/files.js';
import { ITerminalService } from '../../workbench/contrib/terminal/browser/terminal.js';
import { ITerminalBackend, ITerminalBackendRegistry, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, TerminalExtensions, ITerminalProcessOptions, IShellLaunchConfig } from '../../platform/terminal/common/terminal.js';
import { IProcessEnvironment } from '../../base/common/platform.js';
import { Registry } from '../../platform/registry/common/platform.js';
import { InMemoryFileSystemProvider } from '../../platform/files/common/inMemoryFilesystemProvider.js';
import { VSBuffer } from '../../base/common/buffer.js';
@@ -230,16 +234,19 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
private readonly _sessionItems: IChatSessionItem[] = [];
private readonly _itemsChangedEmitter = new Emitter<IChatSessionItemsDelta>();
private readonly _sessionHistory = new Map<string, IChatSessionHistoryItem[]>();
private _worktreeCounter = 0;
constructor(
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@IStorageService private readonly storageService: IStorageService,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@ITerminalService private readonly terminalService: ITerminalService,
) {
super();
this._register(this._itemsChangedEmitter);
this.registerMockAgents();
this.registerMockSessionProvider();
this.registerMockTerminalBackend();
this.preseedFolder();
}
@@ -286,6 +293,7 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
label: message.slice(0, 50) || 'Mock Session',
status: ChatSessionStatus.Completed,
timing: { created: now, lastRequestStarted: now, lastRequestEnded: now },
metadata: { worktreePath: `/mock-worktrees/session-${++this._worktreeCounter}` },
...(changes ? { changes } : {}),
};
this._sessionItems.push(addedOrUpdated);
@@ -311,7 +319,7 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
extensionVersion: '0.0.1',
extensionPublisherId: 'vscode',
extensionDisplayName: 'Sessions E2E Mock',
isDefault: true,
isDefault: agentId === 'copilotcli',
metadata: {},
slashCommands: [],
locations: [ChatAgentLocation.Chat],
@@ -385,11 +393,15 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
},
}));
// Register an item controller so sessions appear in the sidebar list
const items = this._sessionItems;
// Register an item controller so sessions appear in the sidebar list.
// Only copilotcli (Background) sessions need real items — the
// copilot-cloud-agent controller must return an empty array to
// prevent it from overwriting sessions with the wrong providerType
// during a full model resolve.
const controllerItems = scheme === 'copilotcli' ? this._sessionItems : [];
this._register(this.chatSessionsService.registerChatSessionItemController(scheme, {
onDidChangeChatSessionItems: this._itemsChangedEmitter.event,
get items() { return items; },
get items() { return controllerItems; },
async refresh() { /* in-memory, no-op */ },
}));
@@ -400,6 +412,83 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
}
}
private registerMockTerminalBackend(): void {
const terminalService = this.terminalService;
const backend = this.createMockTerminalBackend();
Registry.as<ITerminalBackendRegistry>(TerminalExtensions.Backend).registerTerminalBackend(backend);
terminalService.registerProcessSupport(true);
console.log('[Sessions Web Test] Registered mock terminal backend');
}
private createMockTerminalBackend(): ITerminalBackend {
return {
remoteAuthority: undefined,
isVirtualProcess: false,
onDidRequestDetach: Event.None,
attachToProcess: async () => { throw new Error('Not supported'); },
attachToRevivedProcess: async () => { throw new Error('Not supported'); },
listProcesses: async () => [],
getProfiles: async () => [],
getDefaultProfile: async () => undefined,
getDefaultSystemShell: async () => '/bin/mock-shell',
getShellEnvironment: async () => ({}),
setTerminalLayoutInfo: async () => { },
getTerminalLayoutInfo: async () => undefined,
reduceConnectionGraceTime: () => { },
requestDetachInstance: () => { },
acceptDetachInstanceReply: () => { },
persistTerminalState: () => { },
createProcess: async (_shellLaunchConfig: IShellLaunchConfig, _cwd: string | URI, _cols: number, _rows: number, _unicodeVersion: string, _env: IProcessEnvironment, _options: ITerminalProcessOptions, _shouldPersist: boolean) => {
const onProcessData = new Emitter<string>();
const onProcessReady = new Emitter<IProcessReadyEvent>();
const onProcessExit = new Emitter<number | undefined>();
const onDidChangeHasChildProcesses = new Emitter<boolean>();
const onDidChangeProperty = new Emitter<IProcessProperty<ProcessPropertyType>>();
// Resolve cwd from createProcess arg or shellLaunchConfig
const rawCwd = _cwd || _shellLaunchConfig.cwd;
const cwd = !rawCwd ? '/' : typeof rawCwd === 'string' ? rawCwd : rawCwd.path;
console.log(`[Sessions Web Test] Mock terminal createProcess cwd: '${cwd}' (raw _cwd: '${_cwd}', slc.cwd: '${_shellLaunchConfig.cwd}')`);
// Fire ready after a microtask so the terminal service can wire up listeners
setTimeout(() => {
onProcessReady.fire({ pid: 1, cwd, windowsPty: undefined });
}, 0);
return {
id: 0,
shouldPersist: false,
onProcessData: onProcessData.event,
onProcessReady: onProcessReady.event,
onDidChangeHasChildProcesses: onDidChangeHasChildProcesses.event,
onDidChangeProperty: onDidChangeProperty.event,
onProcessExit: onProcessExit.event,
start: async () => undefined,
shutdown: async () => { },
input: async () => { },
resize: () => { },
clearBuffer: () => { },
acknowledgeDataEvent: () => { },
setUnicodeVersion: async () => { },
getInitialCwd: async () => cwd,
getCwd: async () => cwd,
getLatency: async () => [],
processBinary: async () => { },
refreshProperty: async (property: ProcessPropertyType) => { throw new Error(`Not supported: ${property}`); },
updateProperty: async () => { },
clearUnrespondedRequest: () => { },
};
},
getWslPath: async (original: string, _direction: 'unix-to-win' | 'win-to-unix') => original,
getEnvironment: async () => ({}),
getPerformanceMarks: () => [],
onPtyHostUnresponsive: Event.None,
onPtyHostResponsive: Event.None,
onPtyHostRestart: Event.None,
onPtyHostConnected: Event.None,
} as unknown as ITerminalBackend;
}
private preseedFolder(): void {
const mockFolderUri = URI.from({ scheme: 'mock-fs', authority: 'mock-repo', path: '/mock-repo' }).toString();
this.storageService.store('agentSessions.lastPickedFolder', mockFolderUri, StorageScope.PROFILE, StorageTarget.MACHINE);