StartupCheck.ps1

<#
.SYNOPSIS
    Pre-loads the assembly dependency resolver before CopilotShell.dll loads.
 
.DESCRIPTION
    This script runs BEFORE the binary module (CopilotShell.dll) is loaded by
    PowerShell, via the ScriptsToProcess manifest entry.
 
    It registers a Resolving event handler on the Default AssemblyLoadContext
    that redirects dependency resolution to the dependencies/ subdirectory
    and pre-loads all dependencies into an isolated ALC.
 
    This ensures:
    1. The handler is in place BEFORE PowerShell calls GetTypes() on
       CopilotShell.dll (which triggers resolution of SDK types).
    2. All dependencies share a single isolated ALC with consistent type
       identity — no cross-ALC type conflicts.
    3. On .NET 9 (pwsh 7.5), System.Text.Json v10 is loaded from the module
       instead of falling back to the runtime's v9 — preventing the
       StreamJsonRpc MissingMethodException.
#>


$depsDir = [System.IO.Path]::Combine($PSScriptRoot, 'dependencies')

if (-not [System.IO.Directory]::Exists($depsDir)) {
    # Fallback: dependencies might be in the module root (dev/debug layout)
    return
}

# Create the isolated AssemblyLoadContext for module dependencies.
# All dependencies are loaded here to ensure consistent type identity.
$script:CopilotShellALC = [System.Runtime.Loader.AssemblyLoadContext]::new('CopilotShell', $false)

# Pre-load ALL dependency DLLs into the isolated ALC.
# This ensures that when the SDK and its transitive dependencies need assemblies,
# they find them already in the CopilotShell ALC — preventing fallback to the
# Default ALC which may have older/incompatible versions (e.g. STJ v9 on .NET 9).
foreach ($dll in [System.IO.Directory]::GetFiles($depsDir, '*.dll')) {
    try {
        $script:CopilotShellALC.LoadFromAssemblyPath($dll) | Out-Null
    } catch {
        # Skip DLLs that can't be loaded (e.g. satellite resource assemblies
        # with missing dependencies — they'll be loaded on demand later)
    }
}

# Register the Resolving handler on Default ALC.
# This handler fires when the Default ALC can't resolve a dependency referenced
# by CopilotShell.dll (e.g. GitHub.Copilot.SDK, System.Text.Json v10 on .NET 9).
# It returns the pre-loaded assembly from our isolated ALC.
$script:CopilotShellDepsDir = $depsDir
$script:CopilotShellResolvingHandler = [Func[System.Runtime.Loader.AssemblyLoadContext, System.Reflection.AssemblyName, System.Reflection.Assembly]]{
    param(
        [System.Runtime.Loader.AssemblyLoadContext]$context,
        [System.Reflection.AssemblyName]$assemblyName
    )
    # First check if already loaded in our ALC
    foreach ($loaded in $script:CopilotShellALC.Assemblies) {
        if ($loaded.GetName().Name -eq $assemblyName.Name) {
            return $loaded
        }
    }
    # Try loading from dependencies directory
    $candidatePath = [System.IO.Path]::Combine($script:CopilotShellDepsDir, "$($assemblyName.Name).dll")
    if ([System.IO.File]::Exists($candidatePath)) {
        return $script:CopilotShellALC.LoadFromAssemblyPath($candidatePath)
    }
    return $null
}

[System.Runtime.Loader.AssemblyLoadContext]::Default.add_Resolving($script:CopilotShellResolvingHandler)