feat(vscode): support multi-root workspaces (#1493)

Add proper VS Code multi-root workspace support. New sessions now start in the workspace folder the user chooses instead of always using the first folder.

The VS Code sidebar now shows one shared flat session list for the currently opened workspace folders, keeps that list synced when folders are added or removed, and excludes sessions from worktrees unless that worktree is opened as a workspace folder.

Also keep the OpenCode server process independent from a specific workspace folder so changing the selected folder does not restart or interrupt existing sessions.
This commit is contained in:
Maksym Mospanenko
2026-06-10 23:58:40 +03:00
committed by GitHub
parent 49a1424e5f
commit 7b33805ea0
15 changed files with 646 additions and 96 deletions
+48 -4
View File
@@ -24,6 +24,7 @@ declare global {
__VSCODE_CONFIG__?: {
apiUrl?: string;
workspaceFolder: string;
workspaceFolders?: Array<{ name: string; path: string }>;
theme: string;
connectionStatus: string;
cliAvailable?: boolean;
@@ -1338,12 +1339,55 @@ onCommand('createSessionWithPrompt', (payload) => {
});
});
const normalizeWorkspaceFoldersPayload = (value: unknown): Array<{ name: string; path: string }> => {
if (!Array.isArray(value)) {
return [];
}
return value
.map((entry) => {
const candidate = entry as { name?: unknown; path?: unknown };
const name = typeof candidate.name === 'string' ? candidate.name.trim() : '';
const path = typeof candidate.path === 'string' ? candidate.path.trim() : '';
return path ? { name, path } : null;
})
.filter((entry): entry is { name: string; path: string } => entry !== null);
};
const syncVSCodeWorkspaceProjects = async (
workspaceFolders: Array<{ name: string; path: string }>,
activePath?: string,
) => {
if (window.__VSCODE_CONFIG__) {
window.__VSCODE_CONFIG__.workspaceFolders = workspaceFolders;
}
const { useProjectsStore } = await import('@/stores/useProjectsStore');
return useProjectsStore.getState().syncVSCodeWorkspaceFolders(workspaceFolders, activePath);
};
onCommand('workspaceFoldersChanged', (payload) => {
const record = payload as { workspaceFolders?: unknown } | undefined;
const workspaceFolders = normalizeWorkspaceFoldersPayload(record?.workspaceFolders);
void syncVSCodeWorkspaceProjects(workspaceFolders);
});
// Listen for newSession command from extension title bar button
onCommand('newSession', () => {
import('@/sync/session-ui-store').then(({ useSessionUIStore }) => {
useSessionUIStore.getState().openNewSessionDraft();
onCommand('newSession', (payload) => {
const record = payload as { directory?: unknown; workspaceFolders?: unknown } | undefined;
const directory = record?.directory;
const directoryOverride = typeof directory === 'string' && directory.trim().length > 0 ? directory.trim() : undefined;
const workspaceFolders = normalizeWorkspaceFoldersPayload(record?.workspaceFolders);
Promise.all([
import('@/sync/session-ui-store'),
syncVSCodeWorkspaceProjects(workspaceFolders, directoryOverride),
]).then(([{ useSessionUIStore }, selectedProject]) => {
useSessionUIStore.getState().openNewSessionDraft(
directoryOverride
? { directoryOverride, selectedProjectId: selectedProject?.id ?? undefined }
: undefined
);
});
// Also dispatch event to navigate to chat view in VSCodeLayout
window.dispatchEvent(new CustomEvent('openchamber:navigate', { detail: { view: 'chat' } }));
});