From 2345b4e3c8fa6d8c2d3517a1b1eab98a6effbf70 Mon Sep 17 00:00:00 2001 From: Wiktor Golicz Date: Thu, 22 Oct 2026 08:50:29 +0200 Subject: [PATCH 1/6] fix(muse): enable updates for official Windows launchers --- .../src/server/maintenance.test.ts | 178 +++++++++++++++++- .../provider-muse/src/server/maintenance.ts | 73 +++++-- 2 files changed, 238 insertions(+), 13 deletions(-) diff --git a/packages/provider-muse/src/server/maintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts index 61b1a9fc32f28..a2829874c8d7c 100644 --- a/packages/provider-muse/src/server/maintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -1,10 +1,16 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; +import * as HostProcess from "@t3tools/shared/HostProcess"; +import { resolveSpawnCommand } from "@t3tools/shared/shell"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Stream from "effect/Stream"; import * as HttpClient from "effect/http/HttpClient"; import * as HttpClientResponse from "effect/http/HttpClientResponse"; +import * as ChildProcess from "effect/process/ChildProcess"; +import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; import { compareMuseVersions, @@ -13,7 +19,18 @@ import { parseMuseVersion, } from "./maintenance.ts"; import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; -import { makeManualOnlyProviderMaintenanceCapabilities } from "@t3tools/provider-core/server/maintenanceResolver"; +import { + makeManualOnlyProviderMaintenanceCapabilities, + resolveProviderMaintenanceCapabilitiesEffect, +} from "@t3tools/provider-core/server/maintenanceResolver"; + +const windowsShim = [ + "@echo off", + "setlocal", + '"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0.muse-launcher.ps1" %*', + "exit /b %ERRORLEVEL%", +].join("\r\n"); +const windowsLauncher = "# muse-code/launcher-ps-3\n# MUSE_SYNC_UPDATE\n"; const maintenanceCapabilities = { ...makeManualOnlyProviderMaintenanceCapabilities({ @@ -116,4 +133,163 @@ it.layer(NodeServices.layer)("Muse maintenance", (it) => { }), ), ); + + it.effect("resolves official Windows shims using the instance's path and environment", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }); + const installation = path.join(directory, "Muse R&D (canary) @ ^ ' $ %PATH%"); + yield* fs.makeDirectory(installation); + const shim = path.join(installation, "muse.cmd"); + const launcher = path.join(installation, ".muse-launcher.ps1"); + yield* fs.writeFileString(shim, windowsShim); + yield* fs.writeFileString(launcher, windowsLauncher); + const env = { + PATH: installation, + PATHEXT: ".EXE;.CMD", + SystemRoot: "C:\\Windows", + MUSE_CHANNEL: "muse-canary", + MUSE_NO_AUTO_UPDATE: "1", + MUSE_SYNC_UPDATE: "0", + MUSE_UPDATE_INTERVAL_SECONDS: "3600", + MUSE_AUTH_PATH: "custom credentials", + }; + for (const binaryPath of ["muse", path.join(installation, "muse")]) { + const result = yield* resolveProviderMaintenanceCapabilitiesEffect(museMaintenance, { + binaryPath, + env, + }).pipe(Effect.provideService(HostProcess.Platform, "win32")); + expect(result.update).toMatchObject({ + executable: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", launcher, "--version"], + env: { + ...env, + MUSE_NO_AUTO_UPDATE: "0", + MUSE_SYNC_UPDATE: "1", + MUSE_UPDATE_INTERVAL_SECONDS: "0", + }, + }); + expect(result.update?.lockKey.toLowerCase()).toBe( + `muse:${yield* fs.realPath(shim)}`.toLowerCase(), + ); + expect(result.update?.command).toContain("$env:MUSE_CHANNEL='muse-canary';"); + expect(result.update?.command).toContain("$env:MUSE_NO_AUTO_UPDATE='0';"); + expect(result.update?.command).toContain("$env:MUSE_SYNC_UPDATE='1';"); + expect(result.update?.command).toContain("$env:MUSE_UPDATE_INTERVAL_SECONDS='0';"); + expect(result.update?.command).toContain("'-File' '"); + expect(result.update?.command).toContain(" ^ '' $ %PATH%"); + expect(result.compareVersions).toBe(compareMuseVersions); + } + expect(env.MUSE_NO_AUTO_UPDATE).toBe("1"); + }).pipe(Effect.scoped), + ); + + it.effect("recognizes a direct PowerShell launcher at the real installation path", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }); + const launcher = path.join(directory, ".muse-launcher.ps1"); + yield* fs.writeFileString(launcher, windowsLauncher); + const result = yield* museMaintenance.resolve({ + binaryPath: "muse", + resolvedCommandPath: path.join(directory, "alias.ps1"), + realCommandPath: launcher, + platform: "win32", + env: { SYSTEMROOT: "D:\\Windows" }, + }); + expect(result.update).toMatchObject({ + executable: "D:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", launcher, "--version"], + lockKey: `muse:${launcher}`, + }); + expect(result.update?.env).not.toHaveProperty("MUSE_CHANNEL"); + }).pipe(Effect.scoped), + ); + + it.effect("keeps standalone Windows binaries and unrecognized wrappers manual-only", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }); + const launcher = path.join(directory, ".muse-launcher.ps1"); + for (const [name, source, adjacentSource] of [ + ["muse.exe", windowsLauncher, windowsLauncher], + ["muse.cmd", "@echo off\r\nmuse --version %*", windowsLauncher], + ["muse.cmd", windowsShim.replace("%~dp0", "C:\\other\\"), windowsLauncher], + ["muse.cmd", windowsShim + "\r\nother-command", windowsLauncher], + ["muse.cmd", windowsShim, "# MUSE_SYNC_UPDATE"], + ["muse.cmd", windowsShim, "# muse-code/launcher-ps-3"], + ["muse.cmd", windowsShim, windowsLauncher + " ".repeat(128 * 1_024)], + ["muse.cmd", windowsShim, null], + ["muse.ps1", "# unrecognized wrapper", windowsLauncher], + ] as const) { + const binaryPath = path.join(directory, name); + yield* fs.writeFileString(binaryPath, source); + if (adjacentSource === null) yield* fs.remove(launcher); + else yield* fs.writeFileString(launcher, adjacentSource); + expect( + (yield* museMaintenance.resolve({ + binaryPath, + resolvedCommandPath: binaryPath, + realCommandPath: binaryPath, + platform: "win32", + env: {}, + })).update, + ).toBeNull(); + } + }).pipe(Effect.scoped), + ); }); + +it.live( + "runs a fixture Windows update with literal paths, version arguments and forced environment", + () => + Effect.gen(function* () { + if ((yield* HostProcess.Platform) !== "win32") return; + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = path.join( + yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }), + "Muse R&D (canary) @ ^ ' $; %PATH%", + ); + yield* fs.makeDirectory(directory); + const shim = path.join(directory, "muse.cmd"); + yield* fs.writeFileString(shim, windowsShim); + yield* fs.writeFileString( + path.join(directory, ".muse-launcher.ps1"), + windowsLauncher + + '[Console]::WriteLine(($args -join ",") + "|" + $env:MUSE_CHANNEL + "|" + $env:MUSE_NO_AUTO_UPDATE + "|" + $env:MUSE_SYNC_UPDATE + "|" + $env:MUSE_UPDATE_INTERVAL_SECONDS + "|" + $env:MUSE_AUTH_PATH)', + ); + const capabilities = yield* museMaintenance.resolve({ + binaryPath: "muse", + resolvedCommandPath: shim, + realCommandPath: shim, + platform: "win32", + env: { MUSE_CHANNEL: "muse-canary", MUSE_NO_AUTO_UPDATE: "1", MUSE_AUTH_PATH: "kept" }, + }); + expect(capabilities.update).not.toBeNull(); + const update = capabilities.update!; + const resolved = yield* resolveSpawnCommand(update.executable, update.args); + expect(resolved.shell).toBe(false); + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const child = yield* spawner.spawn( + ChildProcess.make(resolved.command, resolved.args, { + env: update.env, + extendEnv: true, + shell: resolved.shell, + }), + ); + const [stdout, stderr, exitCode] = yield* Effect.all( + [ + child.stdout.pipe(Stream.decodeText(), Stream.mkString), + child.stderr.pipe(Stream.decodeText(), Stream.mkString), + child.exitCode, + ], + { concurrency: "unbounded" }, + ); + expect(Number(exitCode), stderr).toBe(0); + expect(stdout.trim()).toBe("--version|muse-canary|0|1|0|kept"); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); diff --git a/packages/provider-muse/src/server/maintenance.ts b/packages/provider-muse/src/server/maintenance.ts index 0a3a59de128e8..4bf2b853df810 100644 --- a/packages/provider-muse/src/server/maintenance.ts +++ b/packages/provider-muse/src/server/maintenance.ts @@ -1,7 +1,9 @@ import { ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; import { compareSemverVersions } from "@t3tools/shared/semver"; +import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as HttpClient from "effect/http/HttpClient"; import * as HttpClientRequest from "effect/http/HttpClientRequest"; @@ -18,6 +20,14 @@ import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotPr const DRIVER = ProviderDriverKind.make("muse"); const MUSE_CHANNELS = new Set(["muse-stable", "muse-canary"]); +const WINDOWS_LAUNCHER_SHIM = [ + "@echo off", + "setlocal", + '"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0.muse-launcher.ps1" %*', + "exit /b %ERRORLEVEL%", +] + .join("\n") + .toLowerCase(); const ChannelManifest = Schema.Struct({ version: Schema.String.check(Schema.isPattern(/^\d+\.\d+\.\d+-R\d+(?:\.\d+)?$/)), }); @@ -44,15 +54,46 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { ...makeManualOnlyProviderMaintenanceCapabilities({ provider: DRIVER, packageName: null }), compareVersions: compareMuseVersions, }; - if (!context || context.platform === "win32") return manual; + if (!context) return manual; const fs = yield* FileSystem.FileSystem; - const isLauncher = yield* Effect.gen(function* () { + const windows = context.platform === "win32"; + const launcherPath = yield* Effect.gen(function* () { const stat = yield* fs.stat(context.realCommandPath); - if (Number(stat.size) > 128 * 1_024) return false; - const source = yield* fs.readFileString(context.realCommandPath); - return source.includes("muse-code/launcher-") && source.includes("MUSE_SYNC_UPDATE"); - }).pipe(Effect.orElseSucceed(() => false)); - if (!isLauncher) return manual; + if (Number(stat.size) > 128 * 1_024) return null; + let source = yield* fs.readFileString(context.realCommandPath); + let launcher = context.realCommandPath; + if (windows) { + if (/\.(?:cmd|bat)$/i.test(launcher)) { + if (source.trim().replaceAll("\r\n", "\n").toLowerCase() !== WINDOWS_LAUNCHER_SHIM) { + return null; + } + const path = yield* Path.Path; + launcher = path.join(path.dirname(launcher), ".muse-launcher.ps1"); + if (Number((yield* fs.stat(launcher)).size) > 128 * 1_024) return null; + source = yield* fs.readFileString(launcher); + } else if (!/\.ps1$/i.test(launcher)) { + return null; + } + } + return source.includes(windows ? "muse-code/launcher-ps-" : "muse-code/launcher-") && + source.includes("MUSE_SYNC_UPDATE") + ? launcher + : null; + }).pipe(Effect.orElseSucceed(() => null)); + if (!launcherPath) return manual; + let executable = context.resolvedCommandPath; + let args = ["--version"]; + if (windows) { + const hostEnv = yield* HostProcess.Environment; + const systemRoot = [context.env, hostEnv] + .flatMap((env) => Object.entries(env)) + .find(([key, value]) => key.toUpperCase() === "SYSTEMROOT" && value)?.[1]; + executable = systemRoot + ? `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` + : "powershell.exe"; + // -File passes the verified installation's path literally, without cmd parsing it. + args = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", launcherPath, "--version"]; + } // The launcher has no `update` subcommand. Its documented launch environment // forces a synchronous refresh of this executable's own installation. const updateEnv = { @@ -65,12 +106,16 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { ...makeProviderMaintenanceCapabilities({ provider: DRIVER, packageName: null, - updateExecutable: context.resolvedCommandPath, - updateArgs: ["--version"], + updateExecutable: executable, + updateArgs: args, updateCommand: [ - ...Object.entries(updateEnv).map(([key, value]) => `${key}=${shellQuote(value)}`), - shellQuote(context.resolvedCommandPath), - "--version", + ...Object.entries(updateEnv).map(([key, value]) => + windows ? `$env:${key}=${powerShellQuote(value)};` : `${key}=${shellQuote(value)}`, + ), + ...(windows ? ["&"] : []), + ...[executable, ...args].map((word) => + windows ? powerShellQuote(word) : shellQuote(word), + ), ].join(" "), updateLockKey: `muse:${context.realCommandPath}`, platform: context.platform, @@ -85,6 +130,10 @@ function shellQuote(word: string) { return /^[\w./:@=-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`; } +function powerShellQuote(word: string) { + return `'${word.replace(/['\u2018\u2019]/g, "$&$&")}'`; +} + export const latestMuseVersion = Effect.fn("latestMuseVersion")(function* ( environment: NodeJS.ProcessEnv, options?: { readonly fresh?: boolean }, From e4adfbc87e26c3b3d41e24ee43c91a48026613ef Mon Sep 17 00:00:00 2001 From: Wiktor Golicz Date: Thu, 22 Oct 2026 09:19:15 +0200 Subject: [PATCH 2/6] fix(muse): validate Windows launcher structure before updates --- .../src/server/maintenance.test.ts | 73 ++++++++++++++++++- .../provider-muse/src/server/maintenance.ts | 31 +++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/packages/provider-muse/src/server/maintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts index a2829874c8d7c..feb46f20f075a 100644 --- a/packages/provider-muse/src/server/maintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -30,7 +30,35 @@ const windowsShim = [ '"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0.muse-launcher.ps1" %*', "exit /b %ERRORLEVEL%", ].join("\r\n"); -const windowsLauncher = "# muse-code/launcher-ps-3\n# MUSE_SYNC_UPDATE\n"; +const windowsLauncherMarkers = "# muse-code/launcher-ps-3\n# MUSE_SYNC_UPDATE\n"; +// Keep the official launcher's update structure, with inert download functions. +const windowsLauncher = `Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +$launcher_url = 'https://api.meta.ai/muse-launcher.ps1' +$launcher_version = '3' +$user_agent = "muse-code/launcher-ps-$launcher_version" +function Update-Launcher { + param([string]$Self) +} +function Update-Binary { + param([string]$Dir) +} +function Get-ActiveBinary { + param([string]$Dir) + return 'muse.exe' +} +function Invoke-Main { + $self = $PSCommandPath + $dir = Split-Path -Parent $self + if ($env:MUSE_SYNC_UPDATE -eq '1') { + Update-Launcher -Self $self + Update-Binary -Dir $dir + $binary = Get-ActiveBinary -Dir $dir + } +} +Invoke-Main @args +`; const maintenanceCapabilities = { ...makeManualOnlyProviderMaintenanceCapabilities({ @@ -241,6 +269,43 @@ it.layer(NodeServices.layer)("Muse maintenance", (it) => { } }).pipe(Effect.scoped), ); + + it.effect("rejects marker-only Windows launchers and incomplete official update structures", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }); + for (const source of [ + windowsLauncherMarkers, + windowsLauncherMarkers + "Write-Output 'unrecognized wrapper'\n", + windowsLauncher.replace("Set-StrictMode -Version Latest", "# custom wrapper"), + windowsLauncher.replace( + "https://api.meta.ai/muse-launcher.ps1", + "https://example.com/wrapper.ps1", + ), + windowsLauncher.replace("function Update-Binary {", "function Other-Update {"), + windowsLauncher.replace("$self = $PSCommandPath", "$self = 'other.ps1'"), + windowsLauncher.replace("Update-Binary -Dir $dir", "Write-Output 'no update'"), + windowsLauncher.replace("Invoke-Main @args", "Write-Output 'no entry point'"), + ]) { + const launcher = path.join(directory, ".muse-launcher.ps1"); + yield* fs.writeFileString(launcher, source); + const shim = path.join(directory, "muse.cmd"); + yield* fs.writeFileString(shim, windowsShim); + for (const binaryPath of [launcher, shim]) { + expect( + (yield* museMaintenance.resolve({ + binaryPath, + resolvedCommandPath: binaryPath, + realCommandPath: binaryPath, + platform: "win32", + env: {}, + })).update, + ).toBeNull(); + } + } + }).pipe(Effect.scoped), + ); }); it.live( @@ -259,8 +324,10 @@ it.live( yield* fs.writeFileString(shim, windowsShim); yield* fs.writeFileString( path.join(directory, ".muse-launcher.ps1"), - windowsLauncher + - '[Console]::WriteLine(($args -join ",") + "|" + $env:MUSE_CHANNEL + "|" + $env:MUSE_NO_AUTO_UPDATE + "|" + $env:MUSE_SYNC_UPDATE + "|" + $env:MUSE_UPDATE_INTERVAL_SECONDS + "|" + $env:MUSE_AUTH_PATH)', + windowsLauncher.replace( + " $binary = Get-ActiveBinary -Dir $dir", + ' $binary = Get-ActiveBinary -Dir $dir\n [Console]::WriteLine(($args -join ",") + "|" + $env:MUSE_CHANNEL + "|" + $env:MUSE_NO_AUTO_UPDATE + "|" + $env:MUSE_SYNC_UPDATE + "|" + $env:MUSE_UPDATE_INTERVAL_SECONDS + "|" + $env:MUSE_AUTH_PATH)', + ), ); const capabilities = yield* museMaintenance.resolve({ binaryPath: "muse", diff --git a/packages/provider-muse/src/server/maintenance.ts b/packages/provider-muse/src/server/maintenance.ts index 4bf2b853df810..7e6ae4c8d74cd 100644 --- a/packages/provider-muse/src/server/maintenance.ts +++ b/packages/provider-muse/src/server/maintenance.ts @@ -75,10 +75,10 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { return null; } } - return source.includes(windows ? "muse-code/launcher-ps-" : "muse-code/launcher-") && - source.includes("MUSE_SYNC_UPDATE") - ? launcher - : null; + const recognized = windows + ? isOfficialWindowsLauncher(source) + : source.includes("muse-code/launcher-") && source.includes("MUSE_SYNC_UPDATE"); + return recognized ? launcher : null; }).pipe(Effect.orElseSucceed(() => null)); if (!launcherPath) return manual; let executable = context.resolvedCommandPath; @@ -126,6 +126,29 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { }), }; +function isOfficialWindowsLauncher(source: string) { + const normalized = source + .split(/\r?\n/) + .map((line) => line.trim()) + .join("\n") + .trim(); + // The banner alone also appears in wrappers; require the official entry point and updater. + return ( + normalized.startsWith("Set-StrictMode -Version Latest\n$ErrorActionPreference = 'Stop'\n") && + /^\$launcher_version = '\d+'$/m.test(normalized) && + [ + "$launcher_url = 'https://api.meta.ai/muse-launcher.ps1'", + '$user_agent = "muse-code/launcher-ps-$launcher_version"', + "function Update-Launcher {", + "function Update-Binary {", + "function Invoke-Main {", + "$self = $PSCommandPath\n$dir = Split-Path -Parent $self", + "if ($env:MUSE_SYNC_UPDATE -eq '1') {\nUpdate-Launcher -Self $self\nUpdate-Binary -Dir $dir\n$binary = Get-ActiveBinary -Dir $dir", + ].every((signature) => normalized.includes(`\n${signature}\n`)) && + normalized.endsWith("\nInvoke-Main @args") + ); +} + function shellQuote(word: string) { return /^[\w./:@=-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`; } From ca22c1913cb021027b5c622d433cd20582491c4f Mon Sep 17 00:00:00 2001 From: Wiktor Golicz Date: Thu, 22 Oct 2026 09:21:31 +0200 Subject: [PATCH 3/6] fix(muse): escape all PowerShell single quote characters --- .../src/server/maintenance.test.ts | 52 ++++++++++++------- .../provider-muse/src/server/maintenance.ts | 2 +- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/provider-muse/src/server/maintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts index feb46f20f075a..cd0864dd1d8e8 100644 --- a/packages/provider-muse/src/server/maintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -309,7 +309,7 @@ it.layer(NodeServices.layer)("Muse maintenance", (it) => { }); it.live( - "runs a fixture Windows update with literal paths, version arguments and forced environment", + "runs direct and copied Windows update commands with literal paths and forced environment", () => Effect.gen(function* () { if ((yield* HostProcess.Platform) !== "win32") return; @@ -317,7 +317,7 @@ it.live( const path = yield* Path.Path; const directory = path.join( yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }), - "Muse R&D (canary) @ ^ ' $; %PATH%", + "Muse R&D (canary) @ ^ ' \u2018 \u2019 \u201a \u201b $; %PATH%", ); yield* fs.makeDirectory(directory); const shim = path.join(directory, "muse.cmd"); @@ -341,22 +341,36 @@ it.live( const resolved = yield* resolveSpawnCommand(update.executable, update.args); expect(resolved.shell).toBe(false); const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; - const child = yield* spawner.spawn( - ChildProcess.make(resolved.command, resolved.args, { - env: update.env, - extendEnv: true, - shell: resolved.shell, - }), - ); - const [stdout, stderr, exitCode] = yield* Effect.all( - [ - child.stdout.pipe(Stream.decodeText(), Stream.mkString), - child.stderr.pipe(Stream.decodeText(), Stream.mkString), - child.exitCode, - ], - { concurrency: "unbounded" }, - ); - expect(Number(exitCode), stderr).toBe(0); - expect(stdout.trim()).toBe("--version|muse-canary|0|1|0|kept"); + for (const command of [ + resolved, + { + command: update.executable, + // Preserve Unicode through the CLI transport; PowerShell still parses the copied command. + args: [ + "-NoProfile", + "-EncodedCommand", + Buffer.from(update.command, "utf16le").toString("base64"), + ], + shell: false, + }, + ]) { + const child = yield* spawner.spawn( + ChildProcess.make(command.command, command.args, { + env: update.env, + extendEnv: true, + shell: command.shell, + }), + ); + const [stdout, stderr, exitCode] = yield* Effect.all( + [ + child.stdout.pipe(Stream.decodeText(), Stream.mkString), + child.stderr.pipe(Stream.decodeText(), Stream.mkString), + child.exitCode, + ], + { concurrency: "unbounded" }, + ); + expect(Number(exitCode), stderr).toBe(0); + expect(stdout.trim()).toBe("--version|muse-canary|0|1|0|kept"); + } }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); diff --git a/packages/provider-muse/src/server/maintenance.ts b/packages/provider-muse/src/server/maintenance.ts index 7e6ae4c8d74cd..2633487c75c5c 100644 --- a/packages/provider-muse/src/server/maintenance.ts +++ b/packages/provider-muse/src/server/maintenance.ts @@ -154,7 +154,7 @@ function shellQuote(word: string) { } function powerShellQuote(word: string) { - return `'${word.replace(/['\u2018\u2019]/g, "$&$&")}'`; + return `'${word.replace(/['\u2018\u2019\u201a\u201b]/g, "$&$&")}'`; } export const latestMuseVersion = Effect.fn("latestMuseVersion")(function* ( From 4d50dac604303ac62a0af3319b8baac5b7bbb811 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Mon, 26 Oct 2026 15:35:06 +0000 Subject: [PATCH 4/6] fix(muse): resolve and launch configured PowerShell scripts --- .../src/server/maintenanceResolver.ts | 11 +++++++- .../src/server/maintenance.test.ts | 12 ++++----- .../provider-muse/src/server/maintenance.ts | 21 +++++---------- packages/provider-muse/src/server/sdk.test.ts | 14 ++++++++++ packages/provider-muse/src/server/sdk.ts | 18 +++++++++++-- .../provider-muse/src/server/status.test.ts | 26 +++++++++++++++++++ packages/provider-muse/src/server/status.ts | 7 +++-- 7 files changed, 83 insertions(+), 26 deletions(-) diff --git a/packages/provider-core/src/server/maintenanceResolver.ts b/packages/provider-core/src/server/maintenanceResolver.ts index 2e3aad166c58e..31975cc6e0024 100644 --- a/packages/provider-core/src/server/maintenanceResolver.ts +++ b/packages/provider-core/src/server/maintenanceResolver.ts @@ -74,6 +74,8 @@ export type ProviderMaintenanceResolverServices = | ChildProcessSpawner.ChildProcessSpawner; export interface ProviderMaintenanceCapabilitiesResolver { + /** Scripts this resolver launches through an interpreter instead of the OS. */ + readonly windowsScriptExtensions?: ReadonlyArray; readonly resolve: ( context: ProviderMaintenanceResolutionContext | null, ) => Effect.Effect; @@ -721,7 +723,14 @@ export const resolveProviderMaintenanceCapabilitiesEffect = Effect.fn( } const env = options?.env ?? (yield* readCommandLookupEnv); - const resolvedCommandPath = yield* resolveCommandPath(binaryPath, { env }); + const scriptExtension = resolver.windowsScriptExtensions?.find((extension) => + binaryPath.toLowerCase().endsWith(extension), + ); + const lookupEnv = + (yield* HostProcess.Platform) === "win32" && scriptExtension + ? { ...env, PATHEXT: scriptExtension } + : env; + const resolvedCommandPath = yield* resolveCommandPath(binaryPath, { env: lookupEnv }); if (!resolvedCommandPath) { return yield* resolver.resolve(null); } diff --git a/packages/provider-muse/src/server/maintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts index cd0864dd1d8e8..7c96ead7a56d4 100644 --- a/packages/provider-muse/src/server/maintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -213,26 +213,24 @@ it.layer(NodeServices.layer)("Muse maintenance", (it) => { }).pipe(Effect.scoped), ); - it.effect("recognizes a direct PowerShell launcher at the real installation path", () => + it.effect("resolves a configured PowerShell launcher with default Windows command lookup", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-maintenance-" }); const launcher = path.join(directory, ".muse-launcher.ps1"); yield* fs.writeFileString(launcher, windowsLauncher); - const result = yield* museMaintenance.resolve({ - binaryPath: "muse", - resolvedCommandPath: path.join(directory, "alias.ps1"), - realCommandPath: launcher, - platform: "win32", + const result = yield* resolveProviderMaintenanceCapabilitiesEffect(museMaintenance, { + binaryPath: launcher, env: { SYSTEMROOT: "D:\\Windows" }, - }); + }).pipe(Effect.provideService(HostProcess.Platform, "win32")); expect(result.update).toMatchObject({ executable: "D:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", launcher, "--version"], lockKey: `muse:${launcher}`, }); expect(result.update?.env).not.toHaveProperty("MUSE_CHANNEL"); + expect(result.update?.env).not.toHaveProperty("PATHEXT"); }).pipe(Effect.scoped), ); diff --git a/packages/provider-muse/src/server/maintenance.ts b/packages/provider-muse/src/server/maintenance.ts index 2633487c75c5c..b74eda25a4c22 100644 --- a/packages/provider-muse/src/server/maintenance.ts +++ b/packages/provider-muse/src/server/maintenance.ts @@ -1,6 +1,5 @@ import { ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; import { compareSemverVersions } from "@t3tools/shared/semver"; -import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; @@ -17,6 +16,7 @@ import { } from "@t3tools/provider-core/server/maintenanceResolver"; import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotProbe"; +import { museLaunch } from "./sdk.ts"; const DRIVER = ProviderDriverKind.make("muse"); const MUSE_CHANNELS = new Set(["muse-stable", "muse-canary"]); @@ -49,6 +49,7 @@ export const compareMuseVersions = (current: string, latest: string) => /** Only the official launcher owns an update channel; a standalone binary cannot update itself. */ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { + windowsScriptExtensions: [".ps1"], resolve: Effect.fn("MuseMaintenance.resolve")(function* (context) { const manual = { ...makeManualOnlyProviderMaintenanceCapabilities({ provider: DRIVER, packageName: null }), @@ -81,19 +82,11 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { return recognized ? launcher : null; }).pipe(Effect.orElseSucceed(() => null)); if (!launcherPath) return manual; - let executable = context.resolvedCommandPath; - let args = ["--version"]; - if (windows) { - const hostEnv = yield* HostProcess.Environment; - const systemRoot = [context.env, hostEnv] - .flatMap((env) => Object.entries(env)) - .find(([key, value]) => key.toUpperCase() === "SYSTEMROOT" && value)?.[1]; - executable = systemRoot - ? `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` - : "powershell.exe"; - // -File passes the verified installation's path literally, without cmd parsing it. - args = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", launcherPath, "--version"]; - } + const launch = windows + ? yield* museLaunch(launcherPath, context.env) + : { command: context.resolvedCommandPath, args: [] }; + const executable = launch.command; + const args = [...launch.args, "--version"]; // The launcher has no `update` subcommand. Its documented launch environment // forces a synchronous refresh of this executable's own installation. const updateEnv = { diff --git a/packages/provider-muse/src/server/sdk.test.ts b/packages/provider-muse/src/server/sdk.test.ts index c1a11270e35de..d5b78422bbce1 100644 --- a/packages/provider-muse/src/server/sdk.test.ts +++ b/packages/provider-muse/src/server/sdk.test.ts @@ -282,6 +282,20 @@ describe("Muse launch", () => { expect((yield* onWindows("C:\\Muse\\muse.bat", { SYSTEMROOT: "C:\\Windows" })).command).toBe( "C:\\Windows\\System32\\cmd.exe", ); + expect( + yield* museLaunch("C:\\Muse R&D\\.muse-launcher.ps1", { SystemRoot: "D:\\Windows" }).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + ), + ).toEqual({ + command: "D:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + args: [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "C:\\Muse R&D\\.muse-launcher.ps1", + ], + }); expect(yield* onWindows(undefined)).toEqual({ command: "muse", args: [] }); }), ); diff --git a/packages/provider-muse/src/server/sdk.ts b/packages/provider-muse/src/server/sdk.ts index fdd8da3b87eb0..6d4edb6203f9b 100644 --- a/packages/provider-muse/src/server/sdk.ts +++ b/packages/provider-muse/src/server/sdk.ts @@ -87,7 +87,7 @@ function windowsEnvironmentValue(environment: NodeJS.ProcessEnv, name: string) { return key === undefined ? undefined : environment[key]; } -/** Resolve Windows launchers with PATHEXT and run .cmd/.bat under cmd.exe; SDK spawn has no shell. */ +/** The SDK spawns without a shell, so Windows scripts need their interpreter. */ export const museLaunch = Effect.fn("museLaunch")(function* ( binaryPath: string, environment?: NodeJS.ProcessEnv, @@ -95,7 +95,21 @@ export const museLaunch = Effect.fn("museLaunch")(function* ( if ((yield* HostProcess.Platform) !== "win32") return { command: binaryPath, args: [] }; const env = environment ?? (yield* HostProcess.Environment); const resolveExecutable = yield* SpawnExecutableResolution; - const resolved = resolveExecutable(binaryPath, "win32", env) ?? binaryPath; + const resolved = + resolveExecutable( + binaryPath, + "win32", + /\.ps1$/i.test(binaryPath) ? { ...env, PATHEXT: ".PS1" } : env, + ) ?? binaryPath; + if (/\.ps1$/i.test(resolved)) { + const systemRoot = windowsEnvironmentValue(env, "SystemRoot"); + return { + command: systemRoot + ? `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` + : "powershell.exe", + args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", resolved], + }; + } if (!/\.(?:cmd|bat)$/i.test(resolved)) return { command: resolved, args: [] }; const systemRoot = windowsEnvironmentValue(env, "SystemRoot"); return { diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 04f8e1e630d08..5d6cf39baf077 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { MuseSettings } from "../settings.ts"; +import * as HostProcess from "@t3tools/shared/HostProcess"; +import { SpawnExecutableResolution } from "@t3tools/shared/shell"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Schema from "effect/Schema"; @@ -76,6 +78,30 @@ it.layer(NodeServices.layer)("Muse status", (it) => { ), ); + it.effect("checks a configured PowerShell launcher through PowerShell", () => + Effect.gen(function* () { + const binaryPath = "C:\\Muse R&D\\.muse-launcher.ps1"; + const args = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", binaryPath, "--version"]; + const executable = yield* fakeCli( + `if (JSON.stringify(process.argv.slice(2)) !== ${JSON.stringify(JSON.stringify(args))}) process.exit(2); +process.stdout.write("muse 1.0.3-R2198.1\\n");`, + ); + const result = yield* checkMuseProviderStatus( + settings({ enabled: true, binaryPath }), + {}, + undefined, + async () => makeHost(metaCatalog), + ).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + Effect.provideService(SpawnExecutableResolution, (command) => + command === "powershell.exe" ? executable : undefined, + ), + ); + expect(result.status).toBe("ready"); + expect(result.version).toBe("1.0.3-R2198.1"); + }).pipe(Effect.scoped), + ); + it.effect("discovers the Meta catalog without treating it as authentication proof", () => Effect.scoped( Effect.gen(function* () { diff --git a/packages/provider-muse/src/server/status.ts b/packages/provider-muse/src/server/status.ts index bcd910b3573b7..e93fad85e8355 100644 --- a/packages/provider-muse/src/server/status.ts +++ b/packages/provider-muse/src/server/status.ts @@ -7,7 +7,7 @@ import * as Option from "effect/Option"; import * as Result from "effect/Result"; import * as ChildProcess from "effect/process/ChildProcess"; -import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts"; +import { createMuseSdkHost, makeMuseEnvironment, museLaunch } from "./sdk.ts"; import { parseMuseVersion } from "./maintenance.ts"; import { discoverMuseModels, museModelCapabilities } from "./modelCatalog.ts"; import { @@ -70,7 +70,10 @@ export const checkMuseProviderStatus = Effect.fn("checkMuseProviderStatus")(func probe, }); const versionResult = yield* Effect.gen(function* () { - const spawnCommand = yield* resolveSpawnCommand(settings.binaryPath, ["--version"], { + const launch = /\.ps1$/i.test(settings.binaryPath) + ? yield* museLaunch(settings.binaryPath, museEnvironment) + : { command: settings.binaryPath, args: [] }; + const spawnCommand = yield* resolveSpawnCommand(launch.command, [...launch.args, "--version"], { env: museEnvironment, }); return yield* spawnAndCollect( From b6d3f24e1d7ddee6ae60f233e9760a4f3d9e74ea Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Mon, 26 Oct 2026 15:38:37 +0000 Subject: [PATCH 5/6] fix(muse): share Windows installation locks and preserve PowerShell lookup --- packages/provider-muse/src/server/maintenance.test.ts | 2 +- packages/provider-muse/src/server/maintenance.ts | 4 ++-- packages/provider-muse/src/server/sdk.test.ts | 6 ++++++ packages/provider-muse/src/server/sdk.ts | 4 +++- packages/provider-muse/src/server/status.test.ts | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/provider-muse/src/server/maintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts index 7c96ead7a56d4..07b77e973b0db 100644 --- a/packages/provider-muse/src/server/maintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -199,7 +199,7 @@ it.layer(NodeServices.layer)("Muse maintenance", (it) => { }, }); expect(result.update?.lockKey.toLowerCase()).toBe( - `muse:${yield* fs.realPath(shim)}`.toLowerCase(), + `muse:${yield* fs.realPath(launcher)}`.toLowerCase(), ); expect(result.update?.command).toContain("$env:MUSE_CHANNEL='muse-canary';"); expect(result.update?.command).toContain("$env:MUSE_NO_AUTO_UPDATE='0';"); diff --git a/packages/provider-muse/src/server/maintenance.ts b/packages/provider-muse/src/server/maintenance.ts index b74eda25a4c22..3fc88d169b6c7 100644 --- a/packages/provider-muse/src/server/maintenance.ts +++ b/packages/provider-muse/src/server/maintenance.ts @@ -69,7 +69,7 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { return null; } const path = yield* Path.Path; - launcher = path.join(path.dirname(launcher), ".muse-launcher.ps1"); + launcher = yield* fs.realPath(path.join(path.dirname(launcher), ".muse-launcher.ps1")); if (Number((yield* fs.stat(launcher)).size) > 128 * 1_024) return null; source = yield* fs.readFileString(launcher); } else if (!/\.ps1$/i.test(launcher)) { @@ -110,7 +110,7 @@ export const museMaintenance: ProviderMaintenanceCapabilitiesResolver = { windows ? powerShellQuote(word) : shellQuote(word), ), ].join(" "), - updateLockKey: `muse:${context.realCommandPath}`, + updateLockKey: `muse:${launcherPath}`, platform: context.platform, env: { ...context.env, ...updateEnv }, }), diff --git a/packages/provider-muse/src/server/sdk.test.ts b/packages/provider-muse/src/server/sdk.test.ts index d5b78422bbce1..86fc19ceb3942 100644 --- a/packages/provider-muse/src/server/sdk.test.ts +++ b/packages/provider-muse/src/server/sdk.test.ts @@ -296,6 +296,12 @@ describe("Muse launch", () => { "C:\\Muse R&D\\.muse-launcher.ps1", ], }); + expect( + (yield* museLaunch("C:\\Muse\\.muse-launcher.ps1", {}).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + Effect.provideService(HostProcess.Environment, { SYSTEMROOT: "E:\\Windows" }), + )).command, + ).toBe("E:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); expect(yield* onWindows(undefined)).toEqual({ command: "muse", args: [] }); }), ); diff --git a/packages/provider-muse/src/server/sdk.ts b/packages/provider-muse/src/server/sdk.ts index 6d4edb6203f9b..47437bceaecd1 100644 --- a/packages/provider-muse/src/server/sdk.ts +++ b/packages/provider-muse/src/server/sdk.ts @@ -102,7 +102,9 @@ export const museLaunch = Effect.fn("museLaunch")(function* ( /\.ps1$/i.test(binaryPath) ? { ...env, PATHEXT: ".PS1" } : env, ) ?? binaryPath; if (/\.ps1$/i.test(resolved)) { - const systemRoot = windowsEnvironmentValue(env, "SystemRoot"); + const systemRoot = + windowsEnvironmentValue(env, "SystemRoot") ?? + windowsEnvironmentValue(yield* HostProcess.Environment, "SystemRoot"); return { command: systemRoot ? `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 5d6cf39baf077..6394656edfad7 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -94,7 +94,7 @@ process.stdout.write("muse 1.0.3-R2198.1\\n");`, ).pipe( Effect.provideService(HostProcess.Platform, "win32"), Effect.provideService(SpawnExecutableResolution, (command) => - command === "powershell.exe" ? executable : undefined, + command.toLowerCase().endsWith("powershell.exe") ? executable : undefined, ), ); expect(result.status).toBe("ready"); From 509cb79640da48eb9a04013935e892e0690b6a15 Mon Sep 17 00:00:00 2001 From: Wiktor Golicz Date: Thu, 12 Nov 2026 19:26:29 +0100 Subject: [PATCH 6/6] fix(muse): spawn unresolved PowerShell paths directly --- packages/provider-muse/src/server/sdk.test.ts | 9 +++++++++ packages/provider-muse/src/server/sdk.ts | 12 ++++++------ packages/provider-muse/src/server/status.test.ts | 6 +++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/provider-muse/src/server/sdk.test.ts b/packages/provider-muse/src/server/sdk.test.ts index 409589febb34e..be9f0868cdda3 100644 --- a/packages/provider-muse/src/server/sdk.test.ts +++ b/packages/provider-muse/src/server/sdk.test.ts @@ -289,6 +289,7 @@ describe("Muse launch", () => { expect( yield* museLaunch("C:\\Muse R&D\\.muse-launcher.ps1", { SystemRoot: "D:\\Windows" }).pipe( Effect.provideService(HostProcess.Platform, "win32"), + Effect.provideService(SpawnExecutableResolution, (command) => command), ), ).toEqual({ command: "D:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", @@ -304,9 +305,17 @@ describe("Muse launch", () => { (yield* museLaunch("C:\\Muse\\.muse-launcher.ps1", {}).pipe( Effect.provideService(HostProcess.Platform, "win32"), Effect.provideService(HostProcess.Environment, { SYSTEMROOT: "E:\\Windows" }), + Effect.provideService(SpawnExecutableResolution, (command) => command), )).command, ).toBe("E:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); expect(yield* onWindows(undefined)).toEqual({ command: "muse", args: [] }); + const missing = "C:\\Muse\\missing.ps1"; + expect( + yield* museLaunch(missing).pipe( + Effect.provideService(HostProcess.Platform, "win32"), + Effect.provideService(SpawnExecutableResolution, () => undefined), + ), + ).toEqual({ command: missing, args: [] }); }), ); diff --git a/packages/provider-muse/src/server/sdk.ts b/packages/provider-muse/src/server/sdk.ts index 17ef974531253..3c19465cf8876 100644 --- a/packages/provider-muse/src/server/sdk.ts +++ b/packages/provider-muse/src/server/sdk.ts @@ -116,12 +116,12 @@ export const museLaunch = Effect.fn("museLaunch")(function* ( if ((yield* HostProcess.Platform) !== "win32") return { command: binaryPath, args: [] }; const env = environment ?? (yield* HostProcess.Environment); const resolveExecutable = yield* SpawnExecutableResolution; - const resolved = - resolveExecutable( - binaryPath, - "win32", - /\.ps1$/i.test(binaryPath) ? { ...env, PATHEXT: ".PS1" } : env, - ) ?? binaryPath; + const resolved = resolveExecutable( + binaryPath, + "win32", + /\.ps1$/i.test(binaryPath) ? { ...env, PATHEXT: ".PS1" } : env, + ); + if (resolved === undefined) return { command: binaryPath, args: [] }; if (/\.ps1$/i.test(resolved)) { const systemRoot = windowsEnvironmentValue(env, "SystemRoot") ?? diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 0cd8f7976f174..9bd81e927d7b0 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -94,7 +94,11 @@ process.stdout.write("muse 1.0.3-R2198.1\\n");`, ).pipe( Effect.provideService(HostProcess.Platform, "win32"), Effect.provideService(SpawnExecutableResolution, (command) => - command.toLowerCase().endsWith("powershell.exe") ? executable : undefined, + command === binaryPath + ? binaryPath + : command.toLowerCase().endsWith("powershell.exe") + ? executable + : undefined, ), ); expect(result.status).toBe("ready");