agentHost: EH migration - show worktree sessions in a window opened on their repository folder (#330973)

agentHost: show worktree sessions in a window opened on their repository folder
This commit is contained in:
Vijay Upadya
2026-08-15 00:59:08 +00:00
committed by GitHub
parent 50e03bd878
commit 199093dc4b
2 changed files with 57 additions and 1 deletions
@@ -348,6 +348,10 @@ export class AgentHostSessionListStore extends Disposable {
modifiedAt: new Date(session.modifiedTime).toISOString(),
changes: session.changes,
workingDirectories: session.workingDirectories?.map(d => d.toString()),
// The repository root a worktree-isolated session belongs to; the
// workspace filter matches on it because the worktree itself lives
// outside the repository folder.
...(session.project ? { project: { uri: session.project.uri.toString(), displayName: session.project.displayName } } : {}),
// Carry `_meta` so the adoptable-legacy marker survives into the list
// item; consumers use it to avoid passively restoring (and thereby
// migrating) an un-adopted legacy Copilot CLI session.
@@ -371,7 +375,7 @@ export class AgentHostSessionListStore extends Disposable {
/** Uses workspace-file provenance for multi-root workspaces and path containment otherwise. */
private _isSessionInWorkspace(entry: IAgentHostSessionListEntry): boolean {
const workingDirectories = entry.summary.workingDirectories?.map(directory => URI.parse(directory)) ?? [];
const workingDirectories = this._containmentCandidates(entry.summary);
const workspace = this._workspaceContextService.getWorkspace();
const folders = workspace.folders;
const configuration = workspace.configuration;
@@ -398,6 +402,23 @@ export class AgentHostSessionListStore extends Disposable {
);
}
/**
* The directories a session may be matched against a workspace folder by: its
* working directories plus its server-owned project (repository) root. A
* worktree-isolated session runs out of a directory outside the repository
* (`<repo>.worktrees/<name>` for agent-host worktrees, `copilot-worktrees/`
* for legacy extension-host ones), so working directories alone would hide it
* from a window opened on that repository; its project root is the primary
* repository root and restores the match.
*/
private _containmentCandidates(summary: SessionSummary): readonly URI[] {
const candidates = summary.workingDirectories?.map(directory => URI.parse(directory)) ?? [];
if (summary.project?.uri) {
candidates.push(URI.parse(summary.project.uri));
}
return candidates;
}
private _toRemoval(entry: IAgentHostSessionListEntry): IAgentHostSessionListRemoval {
return {
provider: entry.provider,
@@ -3316,6 +3316,41 @@ suite('AgentHostChatContribution', () => {
assert.deepStrictEqual(listController.items.map(item => item.label), ['Contains folder']);
});
test('worktree session is shown in a window opened on its repository folder', async () => {
const { instantiationService, agentHostService } = createTestServices(disposables);
const folder = URI.file('/src/repo');
instantiationService.stub(IWorkspaceContextService, {
getWorkbenchState: () => WorkbenchState.FOLDER,
getWorkspace: () => ({ id: 'folder', folders: [{ uri: folder, name: 'repo', index: 0, toResource: () => folder }] }),
getWorkspaceFolder: () => null,
onDidChangeWorkspaceFolders: Event.None,
});
agentHostService.addSession({
session: AgentSession.uri('copilot', 'worktree'),
startTime: 1000,
modifiedTime: 2000,
summary: 'Worktree session',
// A worktree lives in the `<repo>.worktrees` sibling, never under the folder.
workingDirectories: [URI.file('/src/repo.worktrees/feature')],
project: { uri: folder, displayName: 'repo' },
});
agentHostService.addSession({
session: AgentSession.uri('copilot', 'other-repo-worktree'),
startTime: 1000,
modifiedTime: 2000,
summary: 'Other repo worktree session',
workingDirectories: [URI.file('/src/other.worktrees/feature')],
project: { uri: URI.file('/src/other'), displayName: 'other' },
});
const listController = createSessionListController(disposables, instantiationService, agentHostService);
await listController.refresh(CancellationToken.None);
assert.deepStrictEqual(listController.items.map(item => item.label), ['Worktree session']);
});
test('sessionAdded notification filters out sessions outside the workspace', async () => {
const { instantiationService, agentHostService } = createTestServices(disposables);