From 25c6e79be7179655f0f536605327f7b6174b0491 Mon Sep 17 00:00:00 2001 From: Dolphin Date: Mon, 27 Apr 2026 23:34:50 +0800 Subject: [PATCH] Fix: normalize Windows drive letter in VS Code extension webview paths (#1052) Extract normalizeWindowsDriveLetter to shared pathUtils module Normalize workspaceFolder to uppercase drive letter to match OpenCode server's path normalization. This fixes session visibility issues where VS Code extension sessions don't appear in CLI/desktop and vice versa due to case-sensitive directory comparison on Windows. Changes: - Create pathUtils.ts with shared normalizeWindowsDriveLetter function - Update opencode.ts to import from pathUtils instead of inline definition - Update ChatViewProvider.ts, AgentManagerPanelProvider.ts, SessionEditorPanelProvider.ts to import from pathUtils --- .../vscode/src/AgentManagerPanelProvider.ts | 17 ++++++++++------- packages/vscode/src/ChatViewProvider.ts | 5 ++++- .../vscode/src/SessionEditorPanelProvider.ts | 5 ++++- packages/vscode/src/opencode.ts | 6 +----- packages/vscode/src/pathUtils.ts | 9 +++++++++ 5 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 packages/vscode/src/pathUtils.ts diff --git a/packages/vscode/src/AgentManagerPanelProvider.ts b/packages/vscode/src/AgentManagerPanelProvider.ts index 842800af..70900ebd 100644 --- a/packages/vscode/src/AgentManagerPanelProvider.ts +++ b/packages/vscode/src/AgentManagerPanelProvider.ts @@ -6,12 +6,13 @@ import { getWebviewShikiThemes } from './shikiThemes'; import { getWebviewHtml } from './webviewHtml'; import { openSseProxy } from './sseProxy'; import { resolveWebviewDevServerUrl } from './webviewDevServer'; +import { normalizeWindowsDriveLetter } from './pathUtils'; export class AgentManagerPanelProvider { public static readonly viewType = 'openchamber.agentManager'; private _panel?: vscode.WebviewPanel; - + // Cache latest status/URL for when webview is resolved after connection is ready private _cachedStatus: ConnectionStatus = 'connecting'; private _cachedError?: string; @@ -54,10 +55,10 @@ export class AgentManagerPanelProvider { }; this._panel.webview.html = this._getHtmlForWebview(this._panel.webview); - + // Send theme payload (including optional Shiki theme JSON) after the webview is set up. void this.updateTheme(vscode.window.activeColorTheme.kind); - + // Send cached connection status this._sendCachedState(); @@ -115,16 +116,16 @@ export class AgentManagerPanelProvider { // Cache the latest state this._cachedStatus = status; this._cachedError = error; - + // Send to webview if it exists this._sendCachedState(); } - + private _sendCachedState() { if (!this._panel) { return; } - + this._panel.webview.postMessage({ type: 'connectionStatus', status: this._cachedStatus, @@ -221,7 +222,9 @@ export class AgentManagerPanelProvider { } private _getHtmlForWebview(webview: vscode.Webview): string { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; + const workspaceFolder = normalizeWindowsDriveLetter( + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '' + ); const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false; return getWebviewHtml({ diff --git a/packages/vscode/src/ChatViewProvider.ts b/packages/vscode/src/ChatViewProvider.ts index 84b65869..c30c6fda 100644 --- a/packages/vscode/src/ChatViewProvider.ts +++ b/packages/vscode/src/ChatViewProvider.ts @@ -6,6 +6,7 @@ import { getWebviewShikiThemes } from './shikiThemes'; import { getWebviewHtml } from './webviewHtml'; import { openSseProxy } from './sseProxy'; import { resolveWebviewDevServerUrl } from './webviewDevServer'; +import { normalizeWindowsDriveLetter } from './pathUtils'; export class ChatViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'openchamber.chatView'; @@ -392,7 +393,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { } private _getHtmlForWebview(webview: vscode.Webview) { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; + const workspaceFolder = normalizeWindowsDriveLetter( + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '' + ); // Use cached values which are updated by onStatusChange callback const initialStatus = this._cachedStatus; const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false; diff --git a/packages/vscode/src/SessionEditorPanelProvider.ts b/packages/vscode/src/SessionEditorPanelProvider.ts index d0cf53c3..d6377027 100644 --- a/packages/vscode/src/SessionEditorPanelProvider.ts +++ b/packages/vscode/src/SessionEditorPanelProvider.ts @@ -6,6 +6,7 @@ import { getWebviewShikiThemes } from './shikiThemes'; import { getWebviewHtml } from './webviewHtml'; import { openSseProxy } from './sseProxy'; import { resolveWebviewDevServerUrl } from './webviewDevServer'; +import { normalizeWindowsDriveLetter } from './pathUtils'; type SessionPanelState = { panel: vscode.WebviewPanel; @@ -257,7 +258,9 @@ export class SessionEditorPanelProvider { } private _getHtmlForWebview(webview: vscode.Webview, sessionId: string | null) { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; + const workspaceFolder = normalizeWindowsDriveLetter( + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '' + ); const initialStatus = this._cachedStatus; const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false; diff --git a/packages/vscode/src/opencode.ts b/packages/vscode/src/opencode.ts index dd628ff5..aa1b652a 100644 --- a/packages/vscode/src/opencode.ts +++ b/packages/vscode/src/opencode.ts @@ -7,6 +7,7 @@ import { execSync } from 'child_process'; import { spawnSync } from 'child_process'; import { spawn } from 'child_process'; import { randomBytes } from 'crypto'; +import { normalizeWindowsDriveLetter } from './pathUtils'; const READY_CHECK_TIMEOUT_MS = 30000; export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'; @@ -626,11 +627,6 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo let status: ConnectionStatus = 'disconnected'; let lastError: string | undefined; const listeners = new Set<(status: ConnectionStatus, error?: string) => void>(); - /** On Windows, VS Code's uri.fsPath returns a lowercase drive letter (e.g. d:\...) - * while process.cwd() (used by OpenCode server) returns uppercase (D:\...). - * Normalize to uppercase so session directory queries match. */ - const normalizeWindowsDriveLetter = (p: string): string => - p.replace(/^([a-z]):/, (_, letter: string) => letter.toUpperCase() + ':'); const workspaceDirectory = (): string => normalizeWindowsDriveLetter(vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir()); let workingDirectory: string = workspaceDirectory(); diff --git a/packages/vscode/src/pathUtils.ts b/packages/vscode/src/pathUtils.ts new file mode 100644 index 00000000..85245a9d --- /dev/null +++ b/packages/vscode/src/pathUtils.ts @@ -0,0 +1,9 @@ +/** + * Normalize Windows drive letter to uppercase. + * + * VS Code's `workspaceFolders[0].uri.fsPath` returns lowercase drive letters (e.g. `d:\...`), + * while process.cwd() (used by OpenCode server) returns uppercase (e.g. `D:\...`). + * Normalize to uppercase so session directory queries match. + */ +export const normalizeWindowsDriveLetter = (p: string): string => + p.replace(/^([a-z]):/, (_, letter: string) => letter.toUpperCase() + ':');