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() + ':');