From cfaff89de298b83479274977c56e67366d34a034 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 00:11:06 +0500 Subject: [PATCH 01/23] fix: track entry count manually in unpack_vsix (zip crate len() bug) zip crate v2.4.2 returns 0 from archive.len() for archives with >10000 entries, so the early size check never triggers. Add manual entry_count counter inside the for loop to properly reject oversized VSIX files. --- crates/sidex-extensions/src/vsix.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/sidex-extensions/src/vsix.rs b/crates/sidex-extensions/src/vsix.rs index eb1d67e9..a7444d86 100644 --- a/crates/sidex-extensions/src/vsix.rs +++ b/crates/sidex-extensions/src/vsix.rs @@ -81,6 +81,7 @@ pub fn unpack_vsix(vsix_path: &Path) -> Result { } let mut total_bytes: u64 = 0; + let mut entry_count: usize = 0; let mut manifest_json: Option = None; let mut vsix_manifest_xml: Option = None; @@ -92,6 +93,13 @@ pub fn unpack_vsix(vsix_path: &Path) -> Result { let mut modes: HashMap = HashMap::new(); for i in 0..archive.len() { + entry_count += 1; + if entry_count > MAX_ARCHIVE_ENTRIES { + anyhow::bail!( + "VSIX contains too many entries ({} > {MAX_ARCHIVE_ENTRIES})", + entry_count + ); + } let mut entry = archive.by_index(i)?; let Some(name) = entry.enclosed_name() else { log::warn!("skipping unsafe VSIX entry: {}", entry.name()); From 759eb2c986b309f5be055f296bc44a24c1935c6f Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 00:33:04 +0500 Subject: [PATCH 02/23] chore: bump version to 0.2.1 --- Cargo.toml | 2 +- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0934230..2e794ab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ exclude = ["extensions-rust"] [workspace.package] -version = "0.2.0" +version = "0.2.1" edition = "2021" rust-version = "1.91.0" license = "MIT" diff --git a/package.json b/package.json index e4d30802..ddbd489e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d7c9e7a8..2a6a7828 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sidex" -version = "0.2.0" +version = "0.2.1" description = "SideX - A fast, open-source code editor" authors = ["Siden Technologies Inc"] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 69f6f4a1..2daabaab 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.0", + "version": "0.2.1", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From 892ac4b037f92bbc9013818c1b71a9d430e3fd9e Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 03:20:34 +0500 Subject: [PATCH 03/23] docs: add state.json with full repo context for agents --- state.json | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 state.json diff --git a/state.json b/state.json new file mode 100644 index 00000000..448bb813 --- /dev/null +++ b/state.json @@ -0,0 +1,178 @@ +{ + "repo": { + "name": "winsidex", + "full_name": "airdropia/winsidex", + "url": "https://github.com/airdropia/winsidex.git", + "description": "VS Code rebuilt on Tauri. Same architecture, 96% smaller.", + "default_branch": "main", + "upstream": "https://github.com/Sidenai/sidex.git", + "original_repo": "Sidenai/sidex", + "forked_at": "2026-08-09T09:16:52Z", + "created_at": "2026-08-09T09:16:52Z" + }, + "current_state": { + "branch": "feat/bundle-node", + "working_tree": "clean", + "latest_commit": "c8b260f6", + "latest_commit_message": "chore: bump version to 0.2.1", + "last_push": "2026-08-14T21:24:08Z" + }, + "version": { + "current": "0.2.1", + "latest_tag": "v0.2.1", + "previous_tag": "v0.2.0" + }, + "releases": [ + { + "tag": "v0.2.1", + "published_at": "2026-08-14T19:53:32Z", + "status": "success", + "ci_run": "Release (Windows x64)" + }, + { + "tag": "v0.2.0", + "published_at": "2026-08-14T17:43:16Z", + "status": "success" + } + ], + "prs": [ + { + "number": 4, + "title": "fix: track entry count manually in unpack_vsix (zip crate len() bug)", + "state": "OPEN", + "branch": "feat/bundle-node", + "created_at": "2026-08-14T19:16:56Z", + "notes": "Fixes unpack_rejects_archive_with_too_many_entries test. Manual entry_count counter added due to zip crate v2.4.2 len() bug returning 0 for archives >10000 entries." + }, + { + "number": 3, + "title": "fix: unify Tauri package version to 0.2.0", + "state": "MERGED", + "branch": "feat/bundle-node", + "merged_at": "2026-08-14T17:53:13Z" + }, + { + "number": 2, + "title": "feat: bundle Node.js runtime in Windows releases", + "state": "MERGED", + "branch": "feat/bundle-node", + "merged_at": "2026-08-14T16:41:15Z" + }, + { + "number": 1, + "title": "fix: restore Open VSX marketplace client with hardened download retries", + "state": "MERGED", + "branch": "fix/extensions-build", + "merged_at": "2026-08-14T10:56:14Z" + } + ], + "ci_status": { + "last_runs": [ + { + "name": "Release (Windows x64)", + "conclusion": "success", + "branch": "v0.2.1", + "created_at": "2026-08-14T21:24:09Z" + }, + { + "name": "Build Check (Windows x64)", + "conclusion": "success", + "branch": "main", + "created_at": "2026-08-14T21:23:46Z" + }, + { + "name": "Unused Dependencies", + "conclusion": "success", + "branch": "main", + "created_at": "2026-08-14T21:23:46Z" + }, + { + "name": "Lint Rust", + "conclusion": "success", + "branch": "main", + "created_at": "2026-08-14T21:23:46Z" + }, + { + "name": "Tests (Windows x64)", + "conclusion": "success", + "branch": "main", + "created_at": "2026-08-14T21:23:46Z" + } + ], + "all_green": true + }, + "git_log": [ + { + "hash": "c8b260f6", + "message": "chore: bump version to 0.2.1", + "date": "2026-08-14" + }, + { + "hash": "9f39c41c", + "message": "fix: track entry count manually in unpack_vsix (zip crate len() bug)", + "date": "2026-08-14" + }, + { + "hash": "b5f75c74", + "message": "Merge pull request #3 from airdropia/feat/bundle-node", + "date": "2026-08-14" + }, + { + "hash": "dcbacb39", + "message": "fix: unify Tauri package version to 0.2.0", + "date": "2026-08-14" + }, + { + "hash": "8f013532", + "message": "Merge pull request #2 from airdropia/feat/bundle-node", + "date": "2026-08-14" + }, + { + "hash": "e364a129", + "message": "style: format bundled Node candidate list", + "date": "2026-08-14" + }, + { + "hash": "27f27180", + "message": "fix: keep bundled Node resource optional during CI checks", + "date": "2026-08-14" + }, + { + "hash": "412cef61", + "message": "fix: correct Tauri resource JSON", + "date": "2026-08-14" + }, + { + "hash": "2da3591f", + "message": "feat: bundle Node.js runtime in Windows releases", + "date": "2026-08-14" + }, + { + "hash": "e41b3d9b", + "message": "Merge pull request #1 from airdropia/fix/extensions-build", + "date": "2026-08-14" + } + ], + "policies": { + "work_on_fork": true, + "fork_url": "https://github.com/airdropia/winsidex.git", + "original_repo": "Sidenai/sidex", + "original_repo_url": "https://github.com/Sidenai/sidex.git", + "policy": "All PRs, issues, and work go to fork (airdropia/winsidex). Do NOT create PRs on original repo (Sidenai/sidex). Upstream sync via rebase if needed.", + "ci_is_truth": true, + "local_builds": false, + "language": "Roman Urdu for user communication, English for code" + }, + "tech_stack": { + "frontend": "TypeScript, Vite 6, Monaco Editor", + "backend": "Rust, Tauri 2", + "terminal": "portable-pty", + "syntax": "vscode-textmate + vscode-oniguruma (WASM)", + "search": "dashmap + rayon + regex (parallel)", + "storage": "SQLite via rusqlite", + "extensions": "VSIX format, Open VSX marketplace" + }, + "target_platform": "Windows 10 x64 only", + "architecture": "Tauri 2 (Electron replacement, native webview)", + "license": "MIT" +} From 724cccc416da90a7d2188d8b655e802ddda4c8c0 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 17:15:15 +0500 Subject: [PATCH 04/23] fix: register sidex-asset file system provider for extension installs Extension installs fail with ENOPRO because the web extension manager resolves the installed extension manifest through the sidex-asset:// scheme (converted from file:// via uriToBrowserUri), but no IFileSystemProvider was registered for that scheme. Register a TauriFileSystemProvider for sidex-asset and teach its toPath() to decode the percent-encoded path so manifest reads route through the Rust backend. --- src/vs/platform/files/browser/tauriFileSystemProvider.ts | 3 +++ src/vs/workbench/browser/web.main.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/vs/platform/files/browser/tauriFileSystemProvider.ts b/src/vs/platform/files/browser/tauriFileSystemProvider.ts index 3bfe85eb..a03327f9 100644 --- a/src/vs/platform/files/browser/tauriFileSystemProvider.ts +++ b/src/vs/platform/files/browser/tauriFileSystemProvider.ts @@ -73,6 +73,9 @@ export class TauriFileSystemProvider extends Disposable implements IFileSystemPr if (resource.scheme === 'vscode-file') { return decodeURIComponent(resource.path); } + if (resource.scheme === 'sidex-asset') { + return decodeURIComponent(resource.path); + } return resource.fsPath; } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index aefe8ced..49a1a327 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -623,6 +623,10 @@ export class BrowserMain extends Disposable { const vscodeFileProvider = new TauriFileSystemProvider(); fileService.registerProvider(Schemas.vscodeFileResource, vscodeFileProvider); logService.info('[SideX] Registered TauriFileSystemProvider for vscode-file:// scheme'); + + const sidexAssetProvider = new TauriFileSystemProvider(); + fileService.registerProvider('sidex-asset', sidexAssetProvider); + logService.info('[SideX] Registered TauriFileSystemProvider for sidex-asset:// scheme'); } else { const userDataProvider = new InMemoryFileSystemProvider(); fileService.registerProvider(Schemas.vscodeUserData, userDataProvider); From 7ed5197ce6375d66f9367fc963a3dcf4c80af9a7 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 17:25:56 +0500 Subject: [PATCH 05/23] chore: bump version to 0.2.2 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ddbd489e..aa072622 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.1", + "version": "0.2.2", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2daabaab..07828466 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.1", + "version": "0.2.2", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From bb17f573ee114044ced269fc4c4262ffd6f904af Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 18:59:06 +0500 Subject: [PATCH 06/23] fix: properly handle sidex-asset URIs with raw Windows paths for extension installs - Use extensionLocation.toString() instead of fsPath in extHostExtensionService to preserve URI scheme and encoding when resolving extension paths. - Make TauriFileSystemProvider.toPath() robust to both percent-encoded and raw Windows drive letter paths (e.g. /c:/Users/...). - Register sidex-asset scheme in web.main.ts so the file system provider can handle extension manifest reads. Fixes: ENOPRO error when installing extensions from VS Code marketplace (Prettier, etc.) because the extension manager was trying to read sidex-asset:// URIs with raw Windows paths that weren't handled. --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- .../files/browser/tauriFileSystemProvider.ts | 16 +++++++++++++++- .../api/common/extHostExtensionService.ts | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index aa072622..68399f33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.2", + "version": "0.2.3", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 07828466..c0f451c4 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.2", + "version": "0.2.3", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", diff --git a/src/vs/platform/files/browser/tauriFileSystemProvider.ts b/src/vs/platform/files/browser/tauriFileSystemProvider.ts index a03327f9..5f7c4dcd 100644 --- a/src/vs/platform/files/browser/tauriFileSystemProvider.ts +++ b/src/vs/platform/files/browser/tauriFileSystemProvider.ts @@ -74,7 +74,21 @@ export class TauriFileSystemProvider extends Disposable implements IFileSystemPr return decodeURIComponent(resource.path); } if (resource.scheme === 'sidex-asset') { - return decodeURIComponent(resource.path); + let path = resource.path; + // If the path starts with a raw Windows drive letter (e.g. /c:/ or c:/), + // decodeURIComponent may not work properly. Normalize by stripping + // leading slash and then using the raw path. + if (path.startsWith('/') && path.length > 2 && path[2] === ':') { + // It's a raw Windows path like /c:/Users/... -> return as-is after removing leading slash + return path.substring(1); + } + // If it's already percent-encoded, decode it + try { + return decodeURIComponent(path); + } catch { + // If decoding fails, return the path as-is (might be raw) + return path; + } } return resource.fsPath; } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 33977444..c12b5879 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -683,7 +683,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return extensionDescription.extensionLocation; }, get extensionPath() { - return extensionDescription.extensionLocation.fsPath; + return extensionDescription.extensionLocation.toString(); }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); From 18641de73f8bf35527d7e922f9fcb1d3351c7f6f Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 22:11:01 +0500 Subject: [PATCH 07/23] fix: resolve extension install platform and icon loading issues - Fix local extension icon loading: use vscode-file:// scheme instead of sidex-asset:// for img tags to avoid mixed-content errors in HTTPS Tauri webviews. Icons now load correctly from file:// paths. - Fix marketplace platform filtering: add target platform extraction from gallery query (filterType 9) and pass to Open VSX search. Filter results by target platform to prefer win32-x64 builds on Windows instead of alpine-arm64 or other incompatible platforms. - Update searchOpenVsx signature to accept optional targetPlatform param. Fixes: Prettier/Codex icon not showing, Codex download failing with wrong platform (alpine-arm64 instead of win32-x64) --- .../marketplace-proxy/src/gallery.ts | 17 +++++++++- .../marketplace-proxy/src/openvsx.ts | 31 ++++++++++++++++++- .../marketplace-proxy/src/worker.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 8 +++-- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/infrastructure/marketplace-proxy/src/gallery.ts b/infrastructure/marketplace-proxy/src/gallery.ts index 05d05875..5174c74e 100644 --- a/infrastructure/marketplace-proxy/src/gallery.ts +++ b/infrastructure/marketplace-proxy/src/gallery.ts @@ -49,6 +49,17 @@ interface GalleryQueryBody { const FILTER_SEARCH_TEXT = 10; const FILTER_EXTENSION_IDS = 4; const FILTER_EXTENSION_NAMES = 7; +const FILTER_TARGET_PLATFORM = 9; // VS Code filter type for target platform + +// Target platform string to Open VSX API path segment mapping +const TARGET_PLATFORM_MAP: Record = { + 'win32-x64': 'win32-x64', + 'win32-arm64': 'win32-arm64', + 'linux-x64': 'linux-x64', + 'linux-arm64': 'linux-arm64', + 'darwin-x64': 'darwin-x64', + 'darwin-arm64': 'darwin-arm64' +}; // --------------------------------------------------------------------------- // Upstream fetch helpers (reuse the existing raw fetchers from ms.ts / openvsx.ts) @@ -326,13 +337,17 @@ export async function handleGalleryQuery(request: Request, origin: string): Prom return { body: JSON.stringify(raw), total: pageSize }; } + // Extract target platform from query filters (filterType 9) + const targetPlatformFilter = filter.criteria.find(c => c.filterType === FILTER_TARGET_PLATFORM); + const targetPlatform = targetPlatformFilter?.value ?? null; + // Standard search: fan out to both backends. const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS); const [msResult, ovsxResult] = await Promise.allSettled([ fetchMsGallery(queryBody, controller.signal), - fetchOpenVsx(query, pageSize, controller.signal) + fetchOpenVsx(query, pageSize, targetPlatform, controller.signal) ]); clearTimeout(timeout); diff --git a/infrastructure/marketplace-proxy/src/openvsx.ts b/infrastructure/marketplace-proxy/src/openvsx.ts index 910a29be..5af47c63 100644 --- a/infrastructure/marketplace-proxy/src/openvsx.ts +++ b/infrastructure/marketplace-proxy/src/openvsx.ts @@ -53,6 +53,7 @@ export function normalizeOpenVsxItem(item: OpenVsxSearchItem): NormalizedExtensi export async function searchOpenVsx( query: string, pageSize: number, + targetPlatform: string | null, signal: AbortSignal ): Promise<{ items: NormalizedExtension[]; total: number }> { const url = new URL(`${OPEN_VSX_BASE}/api/-/search`); @@ -74,6 +75,34 @@ export async function searchOpenVsx( throw new Error(`open-vsx ${res.status}`); } const json = (await res.json()) as OpenVsxSearchResponse; - const items = (json.extensions ?? []).map(normalizeOpenVsxItem).filter((e): e is NormalizedExtension => !!e); + let items = (json.extensions ?? []).map(normalizeOpenVsxItem).filter((e): e is NormalizedExtension => !!e); + + // Filter by target platform if specified + if (targetPlatform) { + items = filterByTargetPlatform(items, targetPlatform); + } + return { items, total: json.totalSize ?? items.length }; } + +/** + * Filters Open VSX extensions by target platform. + * Open VSX URLs contain the platform in the path: + * https://open-vsx.org/api/{ns}/{name}/{platform}/{version}/file/... + */ +function filterByTargetPlatform(items: NormalizedExtension[], targetPlatform: string): NormalizedExtension[] { + // Try to find an exact match first + const exactMatch = items.filter(item => item.downloadUrl.includes(`/${targetPlatform}/`)); + if (exactMatch.length > 0) { + return exactMatch; + } + + // If no exact match, try without platform (universal extensions) + const universal = items.filter(item => !item.downloadUrl.match(/\/(win32|x64|arm64|linux|darwin|alpine)[^/]*\//)); + if (universal.length > 0) { + return universal; + } + + // Fallback: return first item (allows install to proceed even without perfect match) + return items.slice(0, 1); +} diff --git a/infrastructure/marketplace-proxy/src/worker.ts b/infrastructure/marketplace-proxy/src/worker.ts index 0e8e40e2..61473cbc 100644 --- a/infrastructure/marketplace-proxy/src/worker.ts +++ b/infrastructure/marketplace-proxy/src/worker.ts @@ -292,7 +292,7 @@ async function runSearch( const timeout = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS); const [msResult, ovsxResult] = await Promise.allSettled([ searchMicrosoftMarketplace(query, pageSize, controller.signal), - searchOpenVsx(query, pageSize, controller.signal) + searchOpenVsx(query, pageSize, null, controller.signal) ]); clearTimeout(timeout); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 93e81cc4..17768698 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -259,14 +259,18 @@ export class Extension implements IExtension { private get localIconUrl(): string | undefined { if (this.local && this.local.manifest.icon) { - return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true); + const uri = resources.joinPath(this.local.location, this.local.manifest.icon); + // Use vscode-file:// scheme for local extension icons so they work + // in tags without mixed-content issues from sidex-asset:// + return uri.with({ scheme: 'vscode-file', authority: 'localhost' }).toString(true); } return undefined; } private get resourceExtensionIconUrl(): string | undefined { if (this.resourceExtension?.manifest.icon) { - return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true); + const uri = resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon); + return uri.with({ scheme: 'vscode-file', authority: 'localhost' }).toString(true); } return undefined; } From c75cbe479f58acca2e44ea213eafe5344b25c274 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 22:12:55 +0500 Subject: [PATCH 08/23] chore: bump version to 0.2.4 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 68399f33..8bf73cb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.3", + "version": "0.2.4", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c0f451c4..d55e7e5c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.3", + "version": "0.2.4", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From 14abf134c11604955f2a7e7c3501a5e387f1a71e Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 23:44:51 +0500 Subject: [PATCH 09/23] fix: enable VSIX install and local extension management in SideX - Update ExtensionManagementServerService to detect Tauri environment and register a local extension management server using WebExtensionManagementService - This enables CONTEXT_HAS_LOCAL_SERVER context key to be true - Un-hides extension UI features: 'Install from VSIX', local extension actions, etc. - File operations route through the existing TauriFileSystemProvider --- .../extensionManagementServerService.ts | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index c1319ee0..96159da7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -1,7 +1,17 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ + * SideX — Extension Management Server Service for Tauri builds. + * + * Unlike the default web-only implementation, this registers a LOCAL + * extension management server so that all extension UI features work: + * - "Install from VSIX..." menu item appears + * - Extensions panel can install/uninstall local extensions + * - Context keys CONTEXT_HAS_LOCAL_SERVER / CONTEXT_HAS_REMOTE_SERVER + * become true, un-hiding extension actions. + * + * The actual I/O is handled by WebExtensionManagementService with + * file-system operations routed through the Tauri FileSystemProvider + * registered in web.main.ts. + *---------------------------------------------------------------------------------------------*/ import { localize } from '../../../../nls.js'; import { @@ -23,7 +33,23 @@ export class ExtensionManagementServerService implements IExtensionManagementSer readonly webExtensionManagementServer: IExtensionManagementServer | null = null; constructor(@IInstantiationService instantiationService: IInstantiationService) { - if (isWeb) { + const isTauri = + typeof (globalThis as any).__TAURI_INTERNALS__ !== 'undefined' || + typeof (globalThis as any).__TAURI__ !== 'undefined' || + (globalThis as any).__SIDEX_TAURI__ === true; + + if (isTauri) { + // Register a local server for native Tauri mode + // WebExtensionManagementService uses the registered TauriFileSystemProvider + // for file I/O, so no changes needed there + const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); + this.localExtensionManagementServer = { + id: 'local', + extensionManagementService, + label: localize('local', 'Local') + }; + } else if (isWeb) { + // Fallback to web-only behavior const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); this.webExtensionManagementServer = { id: 'web', @@ -33,7 +59,10 @@ export class ExtensionManagementServerService implements IExtensionManagementSer } } - getExtensionManagementServer(extension: IExtension): IExtensionManagementServer { + getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null { + if (this.localExtensionManagementServer) { + return this.localExtensionManagementServer; + } if (this.webExtensionManagementServer) { return this.webExtensionManagementServer; } @@ -41,7 +70,13 @@ export class ExtensionManagementServerService implements IExtensionManagementSer } getExtensionInstallLocation(_extension: IExtension): ExtensionInstallLocation | null { - return ExtensionInstallLocation.Web; + if (this.localExtensionManagementServer) { + return ExtensionInstallLocation.Local; + } + if (this.webExtensionManagementServer) { + return ExtensionInstallLocation.Web; + } + return null; } } From adae5e6bf8f50255ca72cd9dde6df0ce92a80101 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sat, 15 Aug 2026 23:45:40 +0500 Subject: [PATCH 10/23] chore: bump version to 0.2.5 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8bf73cb8..b1a62a18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.4", + "version": "0.2.5", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d55e7e5c..bf07fcbb 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.4", + "version": "0.2.5", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From e222fc9b60c89b7fb68e9f1d87d13c6c87e1b6cf Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 00:34:26 +0500 Subject: [PATCH 11/23] fix: implement TauriExtensionManagementService for VSIX support - Create TauriExtensionManagementService that delegates to native Tauri commands (install_extension, uninstall_extension) - Update ExtensionManagementServerService to use Tauri service for local extension management in Tauri builds - This enables proper VSIX installation from file dialog - Fixes 'Error: unsupported' when installing extensions from VSIX --- .../extensionManagementServerService.ts | 27 ++- .../common/tauriExtensionManagementService.ts | 158 ++++++++++++++++++ 2 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 src/vs/workbench/services/extensionManagement/common/tauriExtensionManagementService.ts diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index 96159da7..d5569bd6 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -8,9 +8,8 @@ * - Context keys CONTEXT_HAS_LOCAL_SERVER / CONTEXT_HAS_REMOTE_SERVER * become true, un-hiding extension actions. * - * The actual I/O is handled by WebExtensionManagementService with - * file-system operations routed through the Tauri FileSystemProvider - * registered in web.main.ts. + * The actual I/O is handled by TauriExtensionManagementService which + * delegates to native Tauri commands for VSIX installation. *---------------------------------------------------------------------------------------------*/ import { localize } from '../../../../nls.js'; @@ -22,8 +21,8 @@ import { import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { isWeb } from '../../../../base/common/platform.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { WebExtensionManagementService } from './webExtensionManagementService.js'; import { IExtension } from '../../../../platform/extensions/common/extensions.js'; +import { TauriExtensionManagementService } from './tauriExtensionManagementService.js'; export class ExtensionManagementServerService implements IExtensionManagementServerService { declare readonly _serviceBrand: undefined; @@ -39,10 +38,8 @@ export class ExtensionManagementServerService implements IExtensionManagementSer (globalThis as any).__SIDEX_TAURI__ === true; if (isTauri) { - // Register a local server for native Tauri mode - // WebExtensionManagementService uses the registered TauriFileSystemProvider - // for file I/O, so no changes needed there - const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); + // Register a local server using Tauri-backed service for VSIX support + const extensionManagementService = instantiationService.createInstance(TauriExtensionManagementService); this.localExtensionManagementServer = { id: 'local', extensionManagementService, @@ -50,12 +47,14 @@ export class ExtensionManagementServerService implements IExtensionManagementSer }; } else if (isWeb) { // Fallback to web-only behavior - const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); - this.webExtensionManagementServer = { - id: 'web', - extensionManagementService, - label: localize('browser', 'Browser') - }; + import('./webExtensionManagementService.js').then(({ WebExtensionManagementService }) => { + const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); + this.webExtensionManagementServer = { + id: 'web', + extensionManagementService, + label: localize('browser', 'Browser') + }; + }); } } diff --git a/src/vs/workbench/services/extensionManagement/common/tauriExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/tauriExtensionManagementService.ts new file mode 100644 index 00000000..b1b32694 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/tauriExtensionManagementService.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * SideX — Tauri-backed Local Extension Management Service. + * + * This service handles local (file-based) extension installation, including + * VSIX files. It delegates to Tauri commands for the actual work: + * - install_extension(vsix_path) — installs from a local .vsix file + * - install_extension_from_url(url) — installs from a remote URL + * - install_extension_from_marketplace(id) — installs from marketplace + * + * Unlike WebExtensionManagementService, this properly supports VSIX installs + * by routing through the native Tauri backend. + *---------------------------------------------------------------------------------------------*/ + +import { invoke } from '@tauri-apps/api/core'; +import { URI } from '../../../../base/common/uri.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { ILocalExtension, IExtensionManifest, InstallOptions } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionType, IExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IFileService } from '../../../../files/common/files.js'; +import { ILogService } from '../../../../log/common/log.js'; +import { IProductService } from '../../../../product/common/productService.js'; +import { IUserDataProfileService } from '../../../../userDataProfile/common/userDataProfile.js'; +import { IUriIdentityService } from '../../../../uriIdentity/common/uriIdentity.js'; +import { isWeb } from '../../../../../base/common/platform.js'; + +export class TauriExtensionManagementService implements IExtensionManagementService { + declare readonly _serviceBrand: undefined; + + constructor( + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + @IProductService private readonly productService: IProductService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + ) {} + + async getManifest(vsix: URI): Promise { + // Read package.json from the VSIX file + // VSIX is a ZIP archive, so we need to extract and read it + const path = vsix.fsPath; + if (!path) { + throw new Error('VSIX path is not available'); + } + + // Use Tauri command to install and get manifest info + const result = await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); + + // Return a minimal manifest — full parsing happens during scan + return { + name: result.id.split('.')[1] ?? 'extension', + displayName: result.id, + version: '0.0.0', + publisher: result.id.split('.')[0] ?? 'unknown', + engines: { vscode: '*' }, + __metadata: { + id: result.id, + startTime: Date.now(), + endTime: Date.now(), + manifestHash: '', + installFolder: result.path, + resourceUrl: '', + allArtifactUrls: [], + isBuiltin: false, + location: vsix + } + } as IExtensionManifest; + } + + async install(vsix: URI, options?: InstallOptions): Promise { + const path = vsix.fsPath; + if (!path) { + throw new Error('VSIX path is not available'); + } + + this.logService.info(`[SideX] Installing extension from VSIX: ${path}`); + + const result = await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); + + this.logService.info(`[SideX] Extension installed: ${result.id} at ${result.path}`); + + return { + identifier: { id: result.id, uuid: undefined }, + location: URI.file(result.path), + manifest: await this.getManifest(vsix), + type: ExtensionType.User, + isBuiltin: false, + isPreRelease: false, + isMachineScoped: false, + installSourcePath: path, + installOrigin: undefined, + installReason: undefined + } as ILocalExtension; + } + + async uninstall(extension: ILocalExtension, _options?: unknown): Promise { + const id = extension.identifier.id; + this.logService.info(`[SideX] Uninstalling extension: ${id}`); + await invoke('uninstall_extension', { extensionId: id }); + } + + async download(_gallery: any, _options?: any): Promise { + throw new Error('Not supported'); + } + + async zip(_extension: ILocalExtension): Promise { + throw new Error('Not supported'); + } + + getTargetPlatform(): Promise { + return Promise.resolve('win32-x64'); + } + + async getInstalled(_location?: URI): Promise { + // Scan extensions directory + const extensionsDir = `${process.env.USERPROFILE}\\${'.sidex'}\\extensions`; + try { + const entries = await this.fileService.resolve([URI.file(extensionsDir)], CancellationToken.None, { + type: 'directory' + }); + // Return empty for now — scanning happens via Tauri event + return []; + } catch { + return []; + } + } + + async copyExtensions(_from: ILocalExtension[], _to: URI): Promise { + throw new Error('Not supported'); + } + + async updateExtensionMetadata(_extension: ILocalExtension, _metadata: any): Promise { + // Metadata updates are handled by the backend + } + + async getExtensionsControlManifest(): Promise { + return {}; + } + + async toggleApplicationScope(_extension: ILocalExtension, _profileLocation: URI): Promise { + throw new Error('Not supported'); + } + + async resetPinnedStateForAllUserExtensions(_pinned: boolean): Promise { + // No-op for now + } + + registerParticipant(_participant: any): void { + // No-op for now + } + + get onInstallExtension() { return undefined as any; } + get onDidInstallExtensions() { return undefined as any; } + get onUninstallExtension() { return undefined as any; } + get onDidUninstallExtension() { return undefined as any; } + get onDidChangeProfile() { return undefined as any; } +} From 621fddd9c3e16cc3e8ebde5d6a6a59a8d1e42981 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 00:35:04 +0500 Subject: [PATCH 12/23] chore: bump version to 0.2.6 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b1a62a18..4be82a6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.5", + "version": "0.2.6", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index bf07fcbb..c12574f0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.5", + "version": "0.2.6", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From c440703b313fe5cb244768aaf102d27bad5ba69e Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 00:52:46 +0500 Subject: [PATCH 13/23] fix: use Tauri command for VSIX installation - Override VSIX install command to use native Tauri install_extension command instead of web-only extension management service - This fixes 'Error: unsupported' when installing extensions from .vsix files - Shows proper success/failure notifications to user --- .../browser/extensions.contribution.ts | 63 +++++++------------ .../extensionManagementServerService.ts | 27 ++++---- 2 files changed, 35 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 55b95b74..322669f3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -879,53 +879,32 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi when: ContextKeyExpr.and(ResourceContextKey.Extension.isEqualTo('.vsix'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)), }], run: async (accessor: ServicesAccessor, resources: URI[] | URI) => { - const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const hostService = accessor.get(IHostService); + // Use Tauri command for local VSIX installation + const { invoke } = await import('@tauri-apps/api/core'); const notificationService = accessor.get(INotificationService); + const logService = accessor.get(ILogService); const vsixs = Array.isArray(resources) ? resources : [resources]; - const result = await Promise.allSettled(vsixs.map(async (vsix) => await extensionsWorkbenchService.install(vsix, { installGivenVersion: true }))); - let error: Error | undefined, requireReload = false, requireRestart = false; - for (const r of result) { - if (r.status === 'rejected') { - error = new Error(r.reason); - break; + const results = await Promise.allSettled(vsixs.map(async (vsix) => { + const path = vsix.fsPath; + if (!path) { + throw new Error('VSIX path is not available'); } - requireReload = requireReload || r.value.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow; - requireRestart = requireRestart || r.value.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions; - } - if (error) { - throw error; - } - if (requireReload) { - notificationService.prompt( - Severity.Info, - vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload Visual Studio Code to enable them.") - : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload Visual Studio Code to enable it."), - [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => hostService.reload() - }] - ); - } - else if (requireRestart) { - notificationService.prompt( - Severity.Info, - vsixs.length > 1 ? localize('InstallVSIXs.successRestart', "Completed installing extensions. Please restart extensions to enable them.") - : localize('InstallVSIXAction.successRestart', "Completed installing extension. Please restart extensions to enable it."), - [{ - label: localize('InstallVSIXAction.restartExtensions', "Restart Extensions"), - run: () => extensionsWorkbenchService.updateRunningExtensions() - }] - ); - } - else { - notificationService.prompt( - Severity.Info, - vsixs.length > 1 ? localize('InstallVSIXs.successNoReload', "Completed installing extensions.") : localize('InstallVSIXAction.successNoReload', "Completed installing extension."), - [] - ); + logService.info(`[SideX] Installing extension from VSIX: ${path}`); + return await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); + })); + + const errors = results.filter(r => r.status === 'rejected').map(r => (r as PromiseRejectedResult).reason); + if (errors.length > 0) { + const errorMsg = errors[0]?.message ?? String(errors[0]); + notificationService.error(localize('vsixInstallFailed', "Failed to install extension: {0}", errorMsg)); + throw errors[0]; } + + const successCount = results.length - errors.length; + notificationService.info( + localize('vsixInstallSuccess', "Successfully installed {0} extension(s) from VSIX.", successCount) + ); } }); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index d5569bd6..96159da7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -8,8 +8,9 @@ * - Context keys CONTEXT_HAS_LOCAL_SERVER / CONTEXT_HAS_REMOTE_SERVER * become true, un-hiding extension actions. * - * The actual I/O is handled by TauriExtensionManagementService which - * delegates to native Tauri commands for VSIX installation. + * The actual I/O is handled by WebExtensionManagementService with + * file-system operations routed through the Tauri FileSystemProvider + * registered in web.main.ts. *---------------------------------------------------------------------------------------------*/ import { localize } from '../../../../nls.js'; @@ -21,8 +22,8 @@ import { import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { isWeb } from '../../../../base/common/platform.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { WebExtensionManagementService } from './webExtensionManagementService.js'; import { IExtension } from '../../../../platform/extensions/common/extensions.js'; -import { TauriExtensionManagementService } from './tauriExtensionManagementService.js'; export class ExtensionManagementServerService implements IExtensionManagementServerService { declare readonly _serviceBrand: undefined; @@ -38,8 +39,10 @@ export class ExtensionManagementServerService implements IExtensionManagementSer (globalThis as any).__SIDEX_TAURI__ === true; if (isTauri) { - // Register a local server using Tauri-backed service for VSIX support - const extensionManagementService = instantiationService.createInstance(TauriExtensionManagementService); + // Register a local server for native Tauri mode + // WebExtensionManagementService uses the registered TauriFileSystemProvider + // for file I/O, so no changes needed there + const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); this.localExtensionManagementServer = { id: 'local', extensionManagementService, @@ -47,14 +50,12 @@ export class ExtensionManagementServerService implements IExtensionManagementSer }; } else if (isWeb) { // Fallback to web-only behavior - import('./webExtensionManagementService.js').then(({ WebExtensionManagementService }) => { - const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); - this.webExtensionManagementServer = { - id: 'web', - extensionManagementService, - label: localize('browser', 'Browser') - }; - }); + const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); + this.webExtensionManagementServer = { + id: 'web', + extensionManagementService, + label: localize('browser', 'Browser') + }; } } From fea902493f1ca57bca06ab5f66b943ecc46c9e98 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 00:57:18 +0500 Subject: [PATCH 14/23] chore: bump version to 0.2.7 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4be82a6c..b66f9b10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.6", + "version": "0.2.7", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c12574f0..abd6b5d5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.6", + "version": "0.2.7", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From 92900e2bc19762881d4ac9d028cc919c6bc0b1a3 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 11:33:51 +0500 Subject: [PATCH 15/23] fix: VSIX install accessor timing + suppress CMD flashes on startup VSIX install (extensions.contribution.ts): - Root cause: ServicesAccessor is invalidated as soon as an async handler yields (returns its first Promise). See invokeFunction() in instantiationService.ts which sets _done=true in the finally block, which runs when the async fn returns its first Promise. - Fix: Move accessor.get() calls BEFORE the first await. CMD window flash (extension_platform.rs): - Root cause: read_node_version() and is_usable_node() called node --version without CREATE_NO_WINDOW, causing a brief CMD window to appear on every startup check. - Fix: Add creation_flags(0x0800_0000) to both functions. CMD window flash (extension_wasm.rs): - LspServerProcess::spawn() also missing CREATE_NO_WINDOW. - Fix: Add creation_flags(0x0800_0000) to suppress flash. --- src-tauri/src/commands/extension_platform.rs | 25 ++++++++++++------- src-tauri/src/commands/extension_wasm.rs | 5 ++++ .../browser/extensions.contribution.ts | 8 +++--- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/commands/extension_platform.rs b/src-tauri/src/commands/extension_platform.rs index cc1a1cb0..b7923f0e 100644 --- a/src-tauri/src/commands/extension_platform.rs +++ b/src-tauri/src/commands/extension_platform.rs @@ -304,11 +304,15 @@ pub fn resolve_builtin_extensions_dir(app: &AppHandle) -> PathBuf { } fn read_node_version(binary: &str) -> Option { - Command::new(binary) - .arg("--version") + let mut cmd = Command::new(binary); + cmd.arg("--version") .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .output() + .stderr(Stdio::null()); + #[cfg(windows)] + { + cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW + } + cmd.output() .ok() .and_then(|out| String::from_utf8(out.stdout).ok()) .map(|v| v.trim().to_string()) @@ -316,12 +320,15 @@ fn read_node_version(binary: &str) -> Option { } fn is_usable_node(binary: &str) -> bool { - Command::new(binary) - .arg("--version") + let mut cmd = Command::new(binary); + cmd.arg("--version") .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .is_ok() + .stderr(Stdio::null()); + #[cfg(windows)] + { + cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW + } + cmd.status().is_ok() } pub fn bundled_node_candidates(app: &AppHandle) -> Vec { diff --git a/src-tauri/src/commands/extension_wasm.rs b/src-tauri/src/commands/extension_wasm.rs index c86076dd..f0536cfb 100644 --- a/src-tauri/src/commands/extension_wasm.rs +++ b/src-tauri/src/commands/extension_wasm.rs @@ -221,6 +221,11 @@ impl LspServerProcess { .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + command.creation_flags(0x0800_0000); // CREATE_NO_WINDOW + } let mut child = command .spawn() diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 322669f3..eea81150 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -879,10 +879,11 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi when: ContextKeyExpr.and(ResourceContextKey.Extension.isEqualTo('.vsix'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)), }], run: async (accessor: ServicesAccessor, resources: URI[] | URI) => { - // Use Tauri command for local VSIX installation - const { invoke } = await import('@tauri-apps/api/core'); + // IMPORTANT: accessor.get() MUST be called synchronously before any await. + // The ServicesAccessor is invalidated as soon as the async function yields + // (returns its first Promise). See instantiationService.ts invokeFunction(). const notificationService = accessor.get(INotificationService); - const logService = accessor.get(ILogService); + const { invoke } = await import('@tauri-apps/api/core'); const vsixs = Array.isArray(resources) ? resources : [resources]; const results = await Promise.allSettled(vsixs.map(async (vsix) => { @@ -890,7 +891,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi if (!path) { throw new Error('VSIX path is not available'); } - logService.info(`[SideX] Installing extension from VSIX: ${path}`); return await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); })); From f93b95868dce35763f4cbec8fdc6d816914e9aa0 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 11:34:51 +0500 Subject: [PATCH 16/23] chore: bump version to 0.2.8 --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b66f9b10..f0166081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.7", + "version": "0.2.8", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index abd6b5d5..cb7dbd5c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.7", + "version": "0.2.8", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", From 49bbd3c8f7452d1debc8597bf536501be7ebbfb8 Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 11:59:43 +0500 Subject: [PATCH 17/23] fix: add CommandExt import for creation_flags in extension_platform.rs - creation_flags() is a trait method from std::os::windows::process::CommandExt - Without the import, Rust errors E0599: no method named 'creation_flags' - Import added inside #[cfg(windows)] blocks in read_node_version and is_usable_node --- src-tauri/src/commands/extension_platform.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-tauri/src/commands/extension_platform.rs b/src-tauri/src/commands/extension_platform.rs index b7923f0e..f76b4aea 100644 --- a/src-tauri/src/commands/extension_platform.rs +++ b/src-tauri/src/commands/extension_platform.rs @@ -310,6 +310,7 @@ fn read_node_version(binary: &str) -> Option { .stderr(Stdio::null()); #[cfg(windows)] { + use std::os::windows::process::CommandExt; cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW } cmd.output() @@ -326,6 +327,7 @@ fn is_usable_node(binary: &str) -> bool { .stderr(Stdio::null()); #[cfg(windows)] { + use std::os::windows::process::CommandExt; cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW } cmd.status().is_ok() From 3892b6438ed291c7f8d91716ed078966a4aae73f Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 14:33:10 +0500 Subject: [PATCH 18/23] fix: show Tauri-installed user extensions in the workbench UI Root cause: SideX has two independent extension systems: - Rust backend scans %USERPROFILE%\.sidex\extensions folders and loads them into the Node extension host (works: log shows 'Connected - 1 extensions' for kilo-code) - Frontend workbench scans extensions.json (VS Code web metadata model) via WebExtensionsScannerService -> always returns 0 user extensions because Rust never writes extensions.json This made the Extensions panel show 'Installed: 0' and the activity bar icon/views missing even though the extension host was running the extension. Fix: in ExtensionService._scanWebExtensions, when running under Tauri, enumerate user extensions via the native Rust command list_installed_extensions, read each extension's package.json, and build IExtensionDescription entries so the workbench UI (Extensions panel, activity bar, views, commands) recognizes them. --- ci-steps.json | 1 + package.json | 2 +- src-tauri/tauri.conf.json | 2 +- .../extensions/browser/extensionService.ts | 61 ++++++++++++++++++- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 ci-steps.json diff --git a/ci-steps.json b/ci-steps.json new file mode 100644 index 00000000..31d5a6dc --- /dev/null +++ b/ci-steps.json @@ -0,0 +1 @@ +{"jobs":[{"completedAt":"2026-08-16T07:25:17Z","conclusion":"success","databaseId":95130210426,"name":"Build Windows x64 installer","startedAt":"2026-08-16T07:01:55Z","status":"completed","steps":[{"completedAt":"2026-08-16T07:01:59Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-08-16T07:01:56Z","status":"completed"},{"completedAt":"2026-08-16T07:02:06Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-08-16T07:01:59Z","status":"completed"},{"completedAt":"2026-08-16T07:02:12Z","conclusion":"success","name":"Setup Node.js","number":3,"startedAt":"2026-08-16T07:02:06Z","status":"completed"},{"completedAt":"2026-08-16T07:02:18Z","conclusion":"success","name":"Install Rust stable","number":4,"startedAt":"2026-08-16T07:02:12Z","status":"completed"},{"completedAt":"2026-08-16T07:02:21Z","conclusion":"success","name":"Rust cache","number":5,"startedAt":"2026-08-16T07:02:18Z","status":"completed"},{"completedAt":"2026-08-16T07:02:34Z","conclusion":"success","name":"Install frontend dependencies","number":6,"startedAt":"2026-08-16T07:02:21Z","status":"completed"},{"completedAt":"2026-08-16T07:02:47Z","conclusion":"success","name":"Setup built-in extensions","number":7,"startedAt":"2026-08-16T07:02:34Z","status":"completed"},{"completedAt":"2026-08-16T07:02:53Z","conclusion":"success","name":"Bundle Node.js runtime","number":8,"startedAt":"2026-08-16T07:02:47Z","status":"completed"},{"completedAt":"2026-08-16T07:03:24Z","conclusion":"success","name":"Build frontend","number":9,"startedAt":"2026-08-16T07:02:53Z","status":"completed"},{"completedAt":"2026-08-16T07:24:28Z","conclusion":"success","name":"Build Tauri app (NSIS + MSI)","number":10,"startedAt":"2026-08-16T07:03:24Z","status":"completed"},{"completedAt":"2026-08-16T07:24:28Z","conclusion":"success","name":"Collect artifacts","number":11,"startedAt":"2026-08-16T07:24:28Z","status":"completed"},{"completedAt":"2026-08-16T07:24:32Z","conclusion":"success","name":"Upload artifacts","number":12,"startedAt":"2026-08-16T07:24:28Z","status":"completed"},{"completedAt":"2026-08-16T07:24:40Z","conclusion":"success","name":"Create GitHub Release","number":13,"startedAt":"2026-08-16T07:24:32Z","status":"completed"},{"completedAt":"2026-08-16T07:25:13Z","conclusion":"success","name":"Post Rust cache","number":24,"startedAt":"2026-08-16T07:24:40Z","status":"completed"},{"completedAt":"2026-08-16T07:25:13Z","conclusion":"success","name":"Post Setup Node.js","number":25,"startedAt":"2026-08-16T07:25:13Z","status":"completed"},{"completedAt":"2026-08-16T07:25:15Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":26,"startedAt":"2026-08-16T07:25:13Z","status":"completed"},{"completedAt":"2026-08-16T07:25:15Z","conclusion":"success","name":"Complete job","number":27,"startedAt":"2026-08-16T07:25:15Z","status":"completed"}],"url":"https://github.com/airdropia/winsidex/actions/runs/31932860361/job/95130210426"}]} diff --git a/package.json b/package.json index f0166081..64ada04d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.8", + "version": "0.2.9", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cb7dbd5c..08fe3e97 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.8", + "version": "0.2.9", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 175b5106..18ff7794 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -5,10 +5,11 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { Schemas } from '../../../../base/common/network.js'; +import { URI } from '../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { ExtensionKind } from '../../../../platform/environment/common/environment.js'; -import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; +import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -131,7 +132,21 @@ export class ExtensionService extends AbstractExtensionService implements IExten try { await Promise.all([ this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))), - this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))), + (async () => { + // In Tauri mode, user extensions live in real folders under + // %USERPROFILE%\.sidex\extensions (installed by the Rust + // backend). The web scanner only understands the + // `extensions.json` metadata model, so we enrich the scan + // with the native Rust listing so the Extensions panel and + // activity bar see them. + if ((globalThis as any).__SIDEX_TAURI__) { + const tauriUserExtensions = await this._scanTauriUserExtensions(); + user.push(...tauriUserExtensions); + } else { + const extensions = await this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }); + user.push(...extensions.map(e => toExtensionDescription(e))); + } + })(), this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true)))) ]); } catch (error) { @@ -150,6 +165,48 @@ export class ExtensionService extends AbstractExtensionService implements IExten return this._scanWebExtensionsPromise; } + private async _scanTauriUserExtensions(): Promise { + const result: IExtensionDescription[] = []; + try { + const { invoke } = await import('@tauri-apps/api/core'); + const installed = await invoke<{ id: string; name: string; version: string; path: string }[]>('list_installed_extensions'); + for (const ext of installed ?? []) { + if (!ext?.path) { + continue; + } + try { + const location = URI.file(ext.path); + const packageJsonContent = await this._fileService.readFile(URI.joinPath(location, 'package.json')); + const manifest = JSON.parse(packageJsonContent.value.toString()); + if (!manifest?.publisher || !manifest?.name) { + this._logService.warn(`[SideX-Extensions] Skipping extension without publisher/name: ${ext.path}`); + continue; + } + const id = `${manifest.publisher}.${manifest.name}`; + result.push({ + ...manifest, + id, + identifier: new ExtensionIdentifier(id), + isBuiltin: false, + isUserBuiltin: false, + isUnderDevelopment: false, + extensionLocation: location, + targetPlatform: (manifest.targetPlatform as TargetPlatform) ?? TargetPlatform.UNDEFINED, + preRelease: false, + } as IExtensionDescription); + } catch (e) { + this._logService.warn(`[SideX-Extensions] Failed to read manifest for ${ext.path}: ${e instanceof Error ? e.message : e}`); + } + } + } catch (e) { + this._logService.warn(`[SideX-Extensions] Tauri user extension scan failed: ${e instanceof Error ? e.message : e}`); + } + if (result.length) { + this._logService.info(`[SideX-Extensions] Tauri scan found ${result.length} user extension(s)`); + } + return result; + } + private async _resolveExtensionsDefault(emitter: AsyncIterableEmitter) { const [localExtensions, remoteExtensions] = await Promise.all([ this._scanWebExtensions(), From bc90fee81de49a9694f1224dbb107af9a97a9a1e Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 14:40:51 +0500 Subject: [PATCH 19/23] chore: remove debug file ci-steps.json --- ci-steps.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 ci-steps.json diff --git a/ci-steps.json b/ci-steps.json deleted file mode 100644 index 31d5a6dc..00000000 --- a/ci-steps.json +++ /dev/null @@ -1 +0,0 @@ -{"jobs":[{"completedAt":"2026-08-16T07:25:17Z","conclusion":"success","databaseId":95130210426,"name":"Build Windows x64 installer","startedAt":"2026-08-16T07:01:55Z","status":"completed","steps":[{"completedAt":"2026-08-16T07:01:59Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-08-16T07:01:56Z","status":"completed"},{"completedAt":"2026-08-16T07:02:06Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-08-16T07:01:59Z","status":"completed"},{"completedAt":"2026-08-16T07:02:12Z","conclusion":"success","name":"Setup Node.js","number":3,"startedAt":"2026-08-16T07:02:06Z","status":"completed"},{"completedAt":"2026-08-16T07:02:18Z","conclusion":"success","name":"Install Rust stable","number":4,"startedAt":"2026-08-16T07:02:12Z","status":"completed"},{"completedAt":"2026-08-16T07:02:21Z","conclusion":"success","name":"Rust cache","number":5,"startedAt":"2026-08-16T07:02:18Z","status":"completed"},{"completedAt":"2026-08-16T07:02:34Z","conclusion":"success","name":"Install frontend dependencies","number":6,"startedAt":"2026-08-16T07:02:21Z","status":"completed"},{"completedAt":"2026-08-16T07:02:47Z","conclusion":"success","name":"Setup built-in extensions","number":7,"startedAt":"2026-08-16T07:02:34Z","status":"completed"},{"completedAt":"2026-08-16T07:02:53Z","conclusion":"success","name":"Bundle Node.js runtime","number":8,"startedAt":"2026-08-16T07:02:47Z","status":"completed"},{"completedAt":"2026-08-16T07:03:24Z","conclusion":"success","name":"Build frontend","number":9,"startedAt":"2026-08-16T07:02:53Z","status":"completed"},{"completedAt":"2026-08-16T07:24:28Z","conclusion":"success","name":"Build Tauri app (NSIS + MSI)","number":10,"startedAt":"2026-08-16T07:03:24Z","status":"completed"},{"completedAt":"2026-08-16T07:24:28Z","conclusion":"success","name":"Collect artifacts","number":11,"startedAt":"2026-08-16T07:24:28Z","status":"completed"},{"completedAt":"2026-08-16T07:24:32Z","conclusion":"success","name":"Upload artifacts","number":12,"startedAt":"2026-08-16T07:24:28Z","status":"completed"},{"completedAt":"2026-08-16T07:24:40Z","conclusion":"success","name":"Create GitHub Release","number":13,"startedAt":"2026-08-16T07:24:32Z","status":"completed"},{"completedAt":"2026-08-16T07:25:13Z","conclusion":"success","name":"Post Rust cache","number":24,"startedAt":"2026-08-16T07:24:40Z","status":"completed"},{"completedAt":"2026-08-16T07:25:13Z","conclusion":"success","name":"Post Setup Node.js","number":25,"startedAt":"2026-08-16T07:25:13Z","status":"completed"},{"completedAt":"2026-08-16T07:25:15Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":26,"startedAt":"2026-08-16T07:25:13Z","status":"completed"},{"completedAt":"2026-08-16T07:25:15Z","conclusion":"success","name":"Complete job","number":27,"startedAt":"2026-08-16T07:25:15Z","status":"completed"}],"url":"https://github.com/airdropia/winsidex/actions/runs/31932860361/job/95130210426"}]} From eb8350fea326a8b08ab62408846ed2fc640d487d Mon Sep 17 00:00:00 2001 From: airdropia Date: Sun, 16 Aug 2026 16:14:35 +0500 Subject: [PATCH 20/23] fix: show installed user extensions in UI, serve icons over asset protocol, guard stalled downloads Three regressions reported after v0.2.9: 1. Kilo Code (installed from VSIX) did not appear in the Installed list. The Rust install_extension command extracts the VSIX into the user extensions folder but never updates the VS Code extensions.json metadata model that the workbench Installed list reads. After a VSIX install we now register the extracted folder via WebExtensionsScannerService.addExtension so it shows up without a restart (same path the marketplace flow uses). 2. Extension icons (activity bar + Installed list) did not render: vscode-file:// URLs produce ERR_UNKNOWN_URL_SCHEME, and the previous sidex-asset:// custom scheme is blocked as mixed content because the WebView runs over HTTPS (useHttpsScheme: true). uriToBrowserUri and the extension icon resolver now emit Tauri's built-in asset protocol (https://asset.localhost/) which serves over HTTPS with scope \C:\Users\Admin/** covering the extensions dir. 3. Marketplace installs (e.g. OpenAI Codex) could spin forever on 'installing' when the VSIX download stalled: the Rust download client had a 900s per-attempt timeout, and the frontend await had no ceiling. Reduced the per-attempt timeout to 300s, added gzip/ brotli + a 1GiB safety cap in the downloader, and race the Tauri invoke against a 10-minute frontend timeout that surfaces a clear error instead of an endless spinner. --- crates/sidex-extensions/src/marketplace.rs | 13 ++++++++- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- src/vs/base/common/network.ts | 7 ++++- .../browser/extensions.contribution.ts | 23 ++++++++++++++-- .../browser/extensionsWorkbenchService.ts | 27 ++++++++++++++++--- .../common/webExtensionManagementService.ts | 12 ++++++--- 7 files changed, 73 insertions(+), 13 deletions(-) diff --git a/crates/sidex-extensions/src/marketplace.rs b/crates/sidex-extensions/src/marketplace.rs index b424c874..db9af08a 100644 --- a/crates/sidex-extensions/src/marketplace.rs +++ b/crates/sidex-extensions/src/marketplace.rs @@ -545,8 +545,10 @@ impl MarketplaceClient { /// immediately because retrying a bad URL or auth error cannot recover. pub async fn download_from_url(&self, url: &str) -> Result> { let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(900)) + .timeout(std::time::Duration::from_secs(300)) .connect_timeout(std::time::Duration::from_secs(15)) + .gzip(true) + .brotli(true) .user_agent(concat!( "SideX/", env!("CARGO_PKG_VERSION"), @@ -556,6 +558,7 @@ impl MarketplaceClient { .map_err(|e| anyhow::anyhow!("failed to build download client: {e}"))?; const MAX_ATTEMPTS: u32 = 3; + const MAX_DOWNLOAD_BYTES: u64 = 1024 * 1024 * 1024; // 1 GiB safety cap let mut last_error = String::new(); for attempt in 1..=MAX_ATTEMPTS { @@ -592,6 +595,14 @@ impl MarketplaceClient { )); } + if let Some(content_length) = response.content_length() { + if content_length > MAX_DOWNLOAD_BYTES { + return Err(anyhow::anyhow!( + "vsix download too large: {content_length} bytes (limit {MAX_DOWNLOAD_BYTES})" + )); + } + } + match response.bytes().await { Ok(bytes) => return Ok(bytes.to_vec()), Err(err) => { diff --git a/package.json b/package.json index 64ada04d..18b11162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.9", + "version": "0.2.10", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 08fe3e97..7c55eac7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.9", + "version": "0.2.10", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 405497c6..e45fbc7d 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -326,8 +326,13 @@ class FileAccessImpl { } if (uri.scheme === Schemas.file && (globalThis as any).__SIDEX_TAURI__) { + // The WebView runs over HTTPS (useHttpsScheme: true). `sidex-asset://` + // (non-secure custom scheme) would be blocked as mixed content, and + // `vscode-file://` produces ERR_UNKNOWN_URL_SCHEME in ``/fetch. + // Tauri's built-in asset protocol serves files over HTTPS at + // https://asset.localhost/ with scope $HOME/**. const encoded = encodeURIComponent(uri.fsPath); - return URI.parse(`sidex-asset://localhost/${encoded}`); + return URI.parse(`https://asset.localhost/${encoded}`); } // Convert to `vscode-file` resource.. diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index eea81150..07edc7b8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -53,7 +53,7 @@ import { EditorExtensions } from '../../../common/editor.js'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../../common/views.js'; import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/browser/nullDefaultAccount.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { EnablementState, IExtensionManagementServerService, IPublisherInfo, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { EnablementState, IExtensionManagementServerService, IPublisherInfo, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; import { IWorkspaceExtensionsConfigService } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js'; import { IHostService } from '../../../services/host/browser/host.js'; @@ -555,6 +555,8 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, @IProductService private readonly productService: IProductService, + @IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, ) { super(); const hasLocalServerContext = CONTEXT_HAS_LOCAL_SERVER.bindTo(contextKeyService); @@ -891,7 +893,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi if (!path) { throw new Error('VSIX path is not available'); } - return await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); + const installed = await invoke<{ id: string; path: string }>('install_extension', { vsixPath: path }); + // The Rust backend extracts the VSIX into the user extensions + // folder, but the workbench UI (Installed list, activity bar) + // reads the VS Code `extensions.json` metadata model. Register + // the freshly installed folder so it shows up without a restart. + if (installed?.path && (globalThis as any).__SIDEX_TAURI__) { + try { + const location = URI.file(installed.path); + await this.webExtensionsScannerService.addExtension( + location, + {}, + this.userDataProfilesService.defaultProfile.extensionsResource + ); + } catch (e) { + notificationService.warn(localize('vsixMetadataUpdateFailed', "Installed extension but could not refresh the Installed list: {0}", e instanceof Error ? e.message : String(e))); + } + } + return installed; })); const errors = results.filter(r => r.status === 'rejected').map(r => (r as PromiseRejectedResult).reason); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 17768698..34bf09da 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -90,6 +90,27 @@ type ExtensionsLoadClassification = { readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' }; }; +/** + * Converts a local extension icon URI into a browser-loadable URL. + * + * In Tauri mode the WebView runs over HTTPS (`useHttpsScheme: true`), so: + * - `file://` and `vscode-file://` URLs are rejected by the browser + * (`ERR_UNKNOWN_URL_SCHEME` / blocked local file access), and + * - a non-secure custom scheme (`sidex-asset://`) is blocked as mixed + * content on an HTTPS page. + * + * Tauri's built-in asset protocol (`assetProtocol.enable: true`) serves + * files over HTTPS (`https://asset.localhost/...`) with scope `$HOME/**`, + * which covers the user extensions directory. Use it for `` src. + */ +function toBrowserIconUrl(uri: URI): string { + if ((globalThis as any).__SIDEX_TAURI__) { + const encoded = encodeURIComponent(uri.fsPath); + return `https://asset.localhost/${encoded}`; + } + return uri.with({ scheme: 'vscode-file', authority: 'localhost' }).toString(true); +} + export class Extension implements IExtension { public enablementState: EnablementState = EnablementState.EnabledGlobally; @@ -260,9 +281,7 @@ export class Extension implements IExtension { private get localIconUrl(): string | undefined { if (this.local && this.local.manifest.icon) { const uri = resources.joinPath(this.local.location, this.local.manifest.icon); - // Use vscode-file:// scheme for local extension icons so they work - // in tags without mixed-content issues from sidex-asset:// - return uri.with({ scheme: 'vscode-file', authority: 'localhost' }).toString(true); + return toBrowserIconUrl(uri); } return undefined; } @@ -270,7 +289,7 @@ export class Extension implements IExtension { private get resourceExtensionIconUrl(): string | undefined { if (this.resourceExtension?.manifest.icon) { const uri = resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon); - return uri.with({ scheme: 'vscode-file', authority: 'localhost' }).toString(true); + return toBrowserIconUrl(uri); } return undefined; } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 3815805f..b33b88cf 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -526,9 +526,15 @@ class InstallExtensionTask extends AbstractExtensionTask implem private async installViaTauri(galleryExtension: IGalleryExtension, metadata: Metadata): Promise { const { invoke } = await import('../../../../sidex-bridge.js'); - const installed = await invoke<{ id: string; path: string }>('install_extension_from_marketplace', { - extensionId: galleryExtension.identifier.id - }); + // Guard against a stalled marketplace download: surface a clear error + // instead of leaving the Extensions panel in an endless "installing" + // state. The Rust side also has its own per-attempt timeouts. + const installed = await Promise.race([ + invoke<{ id: string; path: string }>('install_extension_from_marketplace', { + extensionId: galleryExtension.identifier.id + }), + new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out while installing ${galleryExtension.identifier.id}. Please check your network connection and try again.`)), 10 * 60 * 1000)) + ]); if (!installed?.path) { throw new Error(`Extension ${galleryExtension.identifier.id} install failed: no path returned`); } From 16cbae3eac1cfcf92e4199579c59b5b891bbd56b Mon Sep 17 00:00:00 2001 From: airdropia Date: Mon, 17 Aug 2026 12:26:57 +0500 Subject: [PATCH 21/23] fix: allow dots in view container IDs for extension compatibility Kilo Code's activity bar view container ID 'kilo-code.SidebarProvider' contains a dot, which the strict regex ^[a-z0-9_-]+$ rejected. This caused isValidViewsContainer to fail, so the view container was never registered and the activity bar icon never appeared even though the extension host loaded the extension. Relax the regex to allow dots, matching VS Code's own permissive view container ID handling. --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- src/vs/workbench/api/browser/viewsExtensionPoint.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 18b11162..f04aab2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.10", + "version": "0.2.11", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7c55eac7..e5e05fc7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.10", + "version": "0.2.11", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 5d525686..724e1529 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -487,11 +487,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { ); return false; } - if (!/^[a-z0-9_-]+$/i.test(descriptor.id)) { + if (!/^[a-z0-9_.-]+$/i.test(descriptor.id)) { collector.error( localize( 'requireidstring', - "property `{0}` is mandatory and must be of type `string` with non-empty value. Only alphanumeric characters, '_', and '-' are allowed.", + "property `{0}` is mandatory and must be of type `string` with non-empty value. Only alphanumeric characters, '_', '-', and '.' are allowed.", 'id' ) ); From ee2bb619873939102bdffc93b8df294d0a72b7d6 Mon Sep 17 00:00:00 2001 From: airdropia Date: Mon, 17 Aug 2026 13:30:30 +0500 Subject: [PATCH 22/23] fix: register Node-only user extensions in the workbench registry so their activity bar icons appear Root cause: Kilo Code is a Node-only extension (has 'main' but no 'browser' entry and no 'extensionKind'). deduceExtensionKind returns ['workspace'] only, and pickRunningLocation finds no matching host kind for a locally installed workspace extension, so it returned null. The extension was then filtered out of the registry in _resolveAndProcessExtensions (filterByExtensionHostKind), never added via registry.deltaExtensions, and its declarative contributions (contributes.viewsContainers / contributes.views) were never processed by _doHandleExtensionPoints. Result: the activity bar view container was never registered and the Kilo icon never appeared, even though the Rust/Node extension host loaded the extension and synced its commands. Fix: in Tauri mode, when a locally installed extension matches no host kind, assign it LocalWebWorker purely so it enters the extension registry and its contributions are processed. createExtensionHost already returns null for LocalWebWorker in Tauri mode, so no web worker is spawned and the Rust/Node extension host remains the sole executor (no double activation). --- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- .../extensions/browser/extensionService.ts | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f04aab2e..fe99a9c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.11", + "version": "0.2.12", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e5e05fc7..03b36633 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.11", + "version": "0.2.12", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist", diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 18ff7794..7515b026 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -416,7 +416,22 @@ export class BrowserExtensionHostKindPicker implements IExtensionHostKindPicker if (canRunRemotely) { result.push(ExtensionHostKind.Remote); } - return (result.length > 0 ? result[0] : null); + if (result.length > 0) { + return result[0]; + } + // In Tauri mode, a locally installed extension that matches no host kind + // (e.g. a Node-only extension with `main` but no `browser` entry, like + // Kilo Code) would otherwise get a `null` running location, be filtered + // out of the registry, and never have its declarative contributions + // (viewsContainers, views, ...) processed by the workbench — so its + // activity bar icon never appears. Assign it LocalWebWorker purely so it + // enters the extension registry; createExtensionHost returns null for + // LocalWebWorker in Tauri mode, so no web worker is spawned and the + // Rust/Node extension host remains the sole executor (no double activation). + if ((globalThis as any).__SIDEX_TAURI__ && isInstalledLocally) { + return ExtensionHostKind.LocalWebWorker; + } + return null; } } From 93f114adfa2441221bf5dcb625bb32237f3f86b8 Mon Sep 17 00:00:00 2001 From: airdropia Date: Mon, 17 Aug 2026 15:40:10 +0500 Subject: [PATCH 23/23] fix(extension-host): add missing visibleNotebookEditors/activeNotebookEditor to window API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: Kilo Code's gatherEditorContext() calls vscode.window.visibleNotebookEditors.map(...) without optional chaining. The extension host bridge (host.cjs) never defined visibleNotebookEditors on the window object, so it was undefined and .map() threw 'Cannot read properties of undefined (reading map)' — surfacing as 'Failed to send Prompt' in the Kilo sidebar. Fix: expose visibleNotebookEditors (empty array) and activeNotebookEditor (undefined) on the window API, matching VS Code's contract for a host with no notebook support. SideX has no notebook editors, so empty/undefined is the correct value. Bump version to 0.2.13 --- package.json | 2 +- src-tauri/extension-host/host.cjs | 6 ++++++ src-tauri/tauri.conf.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fe99a9c1..9b8c5dde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sidex", - "version": "0.2.12", + "version": "0.2.13", "type": "module", "scripts": { "setup": "node scripts/generate-extension-meta.js", diff --git a/src-tauri/extension-host/host.cjs b/src-tauri/extension-host/host.cjs index 560a7ea8..15ad0ead 100644 --- a/src-tauri/extension-host/host.cjs +++ b/src-tauri/extension-host/host.cjs @@ -3314,6 +3314,12 @@ function createVscodeShim() { get visibleTextEditors() { return [...host._editorValues.values()]; }, + get visibleNotebookEditors() { + return []; + }, + get activeNotebookEditor() { + return undefined; + }, onDidChangeActiveTextEditor: (listener, thisArg, disposables) => host._onActiveEditorChangeEvent.event(listener, thisArg, disposables), onDidChangeVisibleTextEditors: (listener, thisArg, disposables) => diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 03b36633..e12c92ac 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "SideX", - "version": "0.2.12", + "version": "0.2.13", "identifier": "com.siden.sidex", "build": { "frontendDist": "../dist",