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
This commit is contained in:
@@ -6,12 +6,13 @@ import { getWebviewShikiThemes } from './shikiThemes';
|
|||||||
import { getWebviewHtml } from './webviewHtml';
|
import { getWebviewHtml } from './webviewHtml';
|
||||||
import { openSseProxy } from './sseProxy';
|
import { openSseProxy } from './sseProxy';
|
||||||
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
||||||
|
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||||
|
|
||||||
export class AgentManagerPanelProvider {
|
export class AgentManagerPanelProvider {
|
||||||
public static readonly viewType = 'openchamber.agentManager';
|
public static readonly viewType = 'openchamber.agentManager';
|
||||||
|
|
||||||
private _panel?: vscode.WebviewPanel;
|
private _panel?: vscode.WebviewPanel;
|
||||||
|
|
||||||
// Cache latest status/URL for when webview is resolved after connection is ready
|
// Cache latest status/URL for when webview is resolved after connection is ready
|
||||||
private _cachedStatus: ConnectionStatus = 'connecting';
|
private _cachedStatus: ConnectionStatus = 'connecting';
|
||||||
private _cachedError?: string;
|
private _cachedError?: string;
|
||||||
@@ -54,10 +55,10 @@ export class AgentManagerPanelProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this._panel.webview.html = this._getHtmlForWebview(this._panel.webview);
|
this._panel.webview.html = this._getHtmlForWebview(this._panel.webview);
|
||||||
|
|
||||||
// Send theme payload (including optional Shiki theme JSON) after the webview is set up.
|
// Send theme payload (including optional Shiki theme JSON) after the webview is set up.
|
||||||
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
||||||
|
|
||||||
// Send cached connection status
|
// Send cached connection status
|
||||||
this._sendCachedState();
|
this._sendCachedState();
|
||||||
|
|
||||||
@@ -115,16 +116,16 @@ export class AgentManagerPanelProvider {
|
|||||||
// Cache the latest state
|
// Cache the latest state
|
||||||
this._cachedStatus = status;
|
this._cachedStatus = status;
|
||||||
this._cachedError = error;
|
this._cachedError = error;
|
||||||
|
|
||||||
// Send to webview if it exists
|
// Send to webview if it exists
|
||||||
this._sendCachedState();
|
this._sendCachedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sendCachedState() {
|
private _sendCachedState() {
|
||||||
if (!this._panel) {
|
if (!this._panel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._panel.webview.postMessage({
|
this._panel.webview.postMessage({
|
||||||
type: 'connectionStatus',
|
type: 'connectionStatus',
|
||||||
status: this._cachedStatus,
|
status: this._cachedStatus,
|
||||||
@@ -221,7 +222,9 @@ export class AgentManagerPanelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview(webview: vscode.Webview): string {
|
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;
|
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
||||||
|
|
||||||
return getWebviewHtml({
|
return getWebviewHtml({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getWebviewShikiThemes } from './shikiThemes';
|
|||||||
import { getWebviewHtml } from './webviewHtml';
|
import { getWebviewHtml } from './webviewHtml';
|
||||||
import { openSseProxy } from './sseProxy';
|
import { openSseProxy } from './sseProxy';
|
||||||
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
||||||
|
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||||
|
|
||||||
export class ChatViewProvider implements vscode.WebviewViewProvider {
|
export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||||
public static readonly viewType = 'openchamber.chatView';
|
public static readonly viewType = 'openchamber.chatView';
|
||||||
@@ -392,7 +393,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview(webview: vscode.Webview) {
|
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
|
// Use cached values which are updated by onStatusChange callback
|
||||||
const initialStatus = this._cachedStatus;
|
const initialStatus = this._cachedStatus;
|
||||||
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getWebviewShikiThemes } from './shikiThemes';
|
|||||||
import { getWebviewHtml } from './webviewHtml';
|
import { getWebviewHtml } from './webviewHtml';
|
||||||
import { openSseProxy } from './sseProxy';
|
import { openSseProxy } from './sseProxy';
|
||||||
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
||||||
|
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||||
|
|
||||||
type SessionPanelState = {
|
type SessionPanelState = {
|
||||||
panel: vscode.WebviewPanel;
|
panel: vscode.WebviewPanel;
|
||||||
@@ -257,7 +258,9 @@ export class SessionEditorPanelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview(webview: vscode.Webview, sessionId: string | null) {
|
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 initialStatus = this._cachedStatus;
|
||||||
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { execSync } from 'child_process';
|
|||||||
import { spawnSync } from 'child_process';
|
import { spawnSync } from 'child_process';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
|
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||||
|
|
||||||
const READY_CHECK_TIMEOUT_MS = 30000;
|
const READY_CHECK_TIMEOUT_MS = 30000;
|
||||||
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
||||||
@@ -626,11 +627,6 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
let status: ConnectionStatus = 'disconnected';
|
let status: ConnectionStatus = 'disconnected';
|
||||||
let lastError: string | undefined;
|
let lastError: string | undefined;
|
||||||
const listeners = new Set<(status: ConnectionStatus, error?: string) => void>();
|
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 =>
|
const workspaceDirectory = (): string =>
|
||||||
normalizeWindowsDriveLetter(vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir());
|
normalizeWindowsDriveLetter(vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir());
|
||||||
let workingDirectory: string = workspaceDirectory();
|
let workingDirectory: string = workspaceDirectory();
|
||||||
|
|||||||
@@ -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() + ':');
|
||||||
Reference in New Issue
Block a user