diff --git a/.github/workflows/squad-agents-ai-ci.yml b/.github/workflows/squad-agents-ai-ci.yml index f9d257130..e09dbe6c8 100644 --- a/.github/workflows/squad-agents-ai-ci.yml +++ b/.github/workflows/squad-agents-ai-ci.yml @@ -6,6 +6,7 @@ on: - 'src/Squad.Agents.AI/**' - 'test/Squad.Agents.AI.Tests/**' - 'src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/**' + - 'scripts/verify-squad-agents-ai-consumer.ps1' - '.github/workflows/squad-agents-ai-ci.yml' - 'Directory.*.props' - 'Directory.*.json' @@ -17,6 +18,7 @@ on: - 'src/Squad.Agents.AI/**' - 'test/Squad.Agents.AI.Tests/**' - 'src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/**' + - 'scripts/verify-squad-agents-ai-consumer.ps1' - '.github/workflows/squad-agents-ai-ci.yml' - 'Directory.*.props' - 'Directory.*.json' @@ -60,6 +62,50 @@ jobs: - name: Restore sample run: dotnet restore src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj -p:NuGetAudit=true + # Resolves the Copilot CLI version that GitHub.Copilot.SDK (the version this repo + # actually references, $(SquadCopilotSdkVersion)) pins, straight from that package's + # own build/GitHub.Copilot.SDK.props in the local NuGet cache, so the expected/ + # forbidden CLI versions used by the checks below never need to be hardcoded here + # and can't drift out of sync with a future SDK version bump. + # + # CopilotCliVersion is untrusted third-party content (it comes from the + # GitHub.Copilot.SDK NuGet package, not this repo), so it is validated against a + # narrow version grammar before being written to $GITHUB_OUTPUT. Downstream steps + # then read it via env: rather than interpolating the ${{ }} expression directly + # into their run: script bodies, so a malformed or hostile value can never be + # concatenated into, and executed as, PowerShell source. + - name: Resolve expected Copilot CLI version + id: copilot-cli-version + shell: pwsh + run: | + # Same grammar as the version-validation guard in + # scripts/verify-squad-agents-ai-consumer.ps1 (kept in sync by hand: both + # accept dotted release/prerelease CLI versions such as 1.0.71 or 1.0.64-1, + # single line, no surrounding whitespace). + $versionGrammar = '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.]*)?$' + + $sdkVersion = (dotnet msbuild src/Squad.Agents.AI/Squad.Agents.AI.csproj -getProperty:SquadCopilotSdkVersion -nologo).Trim() + if ([string]::IsNullOrWhiteSpace($sdkVersion) -or $sdkVersion -notmatch $versionGrammar) { + throw "SquadCopilotSdkVersion resolved to an empty or invalid value from Squad.Agents.AI.csproj: '$sdkVersion'." + } + + $globalPackagesOutput = dotnet nuget locals global-packages --list + $nugetPackagesRoot = ($globalPackagesOutput -split ':', 2)[1].Trim() + $sdkPropsPath = Join-Path $nugetPackagesRoot "github.copilot.sdk/$sdkVersion/build/GitHub.Copilot.SDK.props" + if (-not (Test-Path $sdkPropsPath)) { + throw "GitHub.Copilot.SDK $sdkVersion props file not found at '$sdkPropsPath'. Was the package restored yet?" + } + + [xml]$sdkProps = Get-Content $sdkPropsPath + $cliVersion = "$($sdkProps.Project.PropertyGroup.CopilotCliVersion)".Trim() + if ([string]::IsNullOrWhiteSpace($cliVersion) -or $cliVersion -notmatch $versionGrammar -or $cliVersion.Contains("`n")) { + throw "CopilotCliVersion parsed from '$sdkPropsPath' is empty, multiline, or does not match the expected grammar ($versionGrammar): '$cliVersion'." + } + + Write-Host "GitHub.Copilot.SDK $sdkVersion pins Copilot CLI $cliVersion" + echo "sdk-version=$sdkVersion" >> $env:GITHUB_OUTPUT + echo "cli-version=$cliVersion" >> $env:GITHUB_OUTPUT + - name: Build package run: dotnet build src/Squad.Agents.AI/Squad.Agents.AI.csproj --no-restore -c Release @@ -69,12 +115,72 @@ jobs: - name: Build sample run: dotnet build src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj --no-restore -c Release + # Regression guard for Reviewer finding #1 (Squad.Agents.AI PR #1519): before the + # Directory.Build.props fix, test/Squad.Agents.AI.Tests and + # src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample downloaded an OLDER Copilot CLI + # (pinned by whatever GitHub.Copilot.SDK version Microsoft.Agents.AI.GitHub.Copilot's + # buildTransitive bridge defaults to) instead of the CLI this repo actually depends + # on, because neither project has a direct PackageReference to GitHub.Copilot.SDK. + - name: Verify no stale Copilot CLI payload in repo builds + shell: pwsh + env: + EXPECTED_COPILOT_CLI_VERSION: ${{ steps.copilot-cli-version.outputs.cli-version }} + run: | + $versionGrammar = '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.]*)?$' + $expected = $env:EXPECTED_COPILOT_CLI_VERSION + if ([string]::IsNullOrWhiteSpace($expected) -or $expected -notmatch $versionGrammar) { + throw "EXPECTED_COPILOT_CLI_VERSION is empty or does not match the expected grammar ($versionGrammar): '$expected'." + } + + $forbidden = @('1.0.67') + $projectObjDirs = @( + 'src/Squad.Agents.AI/obj', + 'test/Squad.Agents.AI.Tests/obj', + 'src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/obj' + ) + + $failed = $false + foreach ($objDir in $projectObjDirs) { + $cliDirs = Get-ChildItem -Path $objDir -Recurse -Directory -Filter 'copilot-cli' -ErrorAction SilentlyContinue + $versions = $cliDirs | ForEach-Object { Get-ChildItem -Path $_.FullName -Directory } | Select-Object -ExpandProperty Name -Unique + + Write-Host "$objDir -> Copilot CLI version(s): $($versions -join ', ')" + + if ($versions -notcontains $expected) { + Write-Host "::error::$objDir did not download the expected Copilot CLI $expected (found: $($versions -join ', '))" + $failed = $true + } + foreach ($bad in $forbidden) { + if ($versions -contains $bad) { + Write-Host "::error::$objDir downloaded forbidden stale Copilot CLI $bad" + $failed = $true + } + } + } + + if ($failed) { + throw "Stale or missing Copilot CLI payload detected, see ::error annotations above." + } + Write-Host "All Squad.Agents.AI-related builds resolved Copilot CLI $expected." + - name: Test run: dotnet test test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj --no-build -c Release --logger trx --results-directory TestResults - name: Pack run: dotnet pack src/Squad.Agents.AI/Squad.Agents.AI.csproj --no-build -c Release -o nupkgs + # Hermetic consumer-level packaging check (Reviewer finding #1): proves the fix also + # works for a REAL external NuGet consumer of Squad.Agents.AI, not just this repo's + # own ProjectReference-based test/sample projects above (which Directory.Build.props + # alone would cover even without the buildTransitive/Squad.Agents.AI.props fix in + # Squad.Agents.AI.csproj). See scripts/verify-squad-agents-ai-consumer.ps1. + - name: Verify Squad.Agents.AI consumer packaging (native Copilot CLI payload) + shell: pwsh + env: + EXPECTED_COPILOT_CLI_VERSION: ${{ steps.copilot-cli-version.outputs.cli-version }} + run: | + ./scripts/verify-squad-agents-ai-consumer.ps1 -NupkgsDir nupkgs -ExpectedCopilotCliVersion $env:EXPECTED_COPILOT_CLI_VERSION + - name: Upload test results if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..83c0f1e50 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,64 @@ + + + + + 1.0.7 + + + <_MicrosoftAgentsAICopilotSdkVersion Condition="'$(MSBuildProjectName)' != 'Squad.Agents.AI'">$(SquadCopilotSdkVersion) + + diff --git a/scripts/verify-squad-agents-ai-consumer.ps1 b/scripts/verify-squad-agents-ai-consumer.ps1 new file mode 100644 index 000000000..4735d5748 --- /dev/null +++ b/scripts/verify-squad-agents-ai-consumer.ps1 @@ -0,0 +1,311 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Hermetic consumer-level packaging check for Squad.Agents.AI. + +.DESCRIPTION + Restores a scratch consumer project against ONLY the locally-produced Squad.Agents.AI + nupkg (plus nuget.org for its transitive dependencies), builds it for the current + runner's default portable RID, and asserts that the native Copilot CLI payload (the + copilot/copilot.exe executable and its runtime.node-derived native library) actually + propagated to a REAL PackageReference consumer, not just to this repo's own + ProjectReference-based test/sample projects. + + This exists because Microsoft.Agents.AI.GitHub.Copilot ships GitHub.Copilot.SDK's CLI + download targets to transitive consumers via a buildTransitive/ bridge that, unless + overridden, resolves an OLDER GitHub.Copilot.SDK version (whatever version the adapter + package itself was built/floor-pinned against) instead of the version Squad.Agents.AI + actually references. See Directory.Build.props and the GenerateCopilotSdkVersionBridge + target in src/Squad.Agents.AI/Squad.Agents.AI.csproj for the fix; this script is the + regression test for it. A green run here is the ONLY thing that proves the fix works for + real external NuGet consumers, since this repo's own test/sample projects are covered by + Directory.Build.props alone (a repo-internal mechanism a real external consumer never + sees). + + Does not execute the downloaded Copilot CLI binary, only inspects file paths that + MSBuild's own download/copy targets produced, so it never runs untrusted downloaded + code. Does not touch package source or TLS/signature verification policy: only two + well-known, standard sources are used (nuget.org over HTTPS, and the local nupkgs + folder produced by `dotnet pack` in this same repo checkout). The Squad.Agents.AI + package itself is pinned to the local source only, via NuGet Package Source Mapping, + so it can never be silently satisfied by an identically named/versioned package on + nuget.org; a post-restore SHA512 hash comparison against the exact bytes this script + just packed is an additional, independent proof of the same thing. + + All XML this script generates (the scratch .csproj and nuget.config) is built with + System.Xml.Linq (XElement/XAttribute/XDocument), which escapes every interpolated value + according to the XML specification. Nothing is embedded via ad hoc string + concatenation or hand-written entity substitution. + +.PARAMETER NupkgsDir + Directory containing exactly one packed Squad.Agents.AI .nupkg (e.g. the CI job's + existing `dotnet pack ... -o nupkgs` output). The package version is parsed from the + .nupkg filename itself, so it can never drift out of sync with whatever version was + actually packed. + +.PARAMETER ExpectedCopilotCliVersion + The Copilot CLI version the consumer is expected to download (the CLI version pinned by + the GitHub.Copilot.SDK version Squad.Agents.AI references), e.g. "1.0.71". Must match + the same narrow version grammar validated in squad-agents-ai-ci.yml; this script + re-validates it independently since it is untrusted content that ultimately comes from + a third-party NuGet package. + +.PARAMETER ForbiddenCopilotCliVersions + Copilot CLI versions that must NOT appear anywhere in the consumer's build output, + i.e. the stale versions this script exists to catch a regression to. + +.PARAMETER TargetFramework + Target framework moniker for the scratch consumer project. +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$NupkgsDir, + + [Parameter(Mandatory = $true)] + [string]$ExpectedCopilotCliVersion, + + [string[]]$ForbiddenCopilotCliVersions = @('1.0.67'), + + [string]$TargetFramework = 'net10.0' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +Add-Type -AssemblyName System.Xml.Linq + +# Same grammar as the version-validation guard in squad-agents-ai-ci.yml (kept in sync by +# hand: both accept dotted release/prerelease CLI versions such as 1.0.71 or 1.0.64-1, +# single line, no surrounding whitespace). CopilotCliVersion ultimately comes from a +# third-party NuGet package, so it is treated as untrusted input here too, independent of +# whatever validation the caller already performed. +$VersionGrammar = '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.]*)?$' + +function Assert-ValidVersion([string]$Value, [string]$Description) { + if ([string]::IsNullOrWhiteSpace($Value) -or $Value.Contains("`n") -or $Value.Contains("`r") -or ($Value -notmatch $VersionGrammar)) { + throw "$Description is empty, multiline, or does not match the expected grammar ($VersionGrammar): '$Value'." + } +} + +function Get-Sha512Base64([string]$FilePath) { + $hasher = [System.Security.Cryptography.SHA512]::Create() + try { + $stream = [System.IO.File]::OpenRead($FilePath) + try { + return [Convert]::ToBase64String($hasher.ComputeHash($stream)) + } + finally { + $stream.Dispose() + } + } + finally { + $hasher.Dispose() + } +} + +function Write-Section([string]$Title) { + Write-Host "" + Write-Host "=== $Title ===" -ForegroundColor Cyan +} + +Assert-ValidVersion -Value $ExpectedCopilotCliVersion -Description 'ExpectedCopilotCliVersion' +foreach ($forbidden in $ForbiddenCopilotCliVersions) { + Assert-ValidVersion -Value $forbidden -Description 'ForbiddenCopilotCliVersions entry' +} + +$nupkgsFull = (Resolve-Path $NupkgsDir).Path +$candidateNupkgs = @(Get-ChildItem -Path $nupkgsFull -Filter "squad.agents.ai.*.nupkg") +if ($candidateNupkgs.Count -eq 0) { + throw "No 'squad.agents.ai.*.nupkg' found in '$nupkgsFull'. Did the Pack step run before this check?" +} +if ($candidateNupkgs.Count -gt 1) { + throw "Expected exactly one packed Squad.Agents.AI nupkg in '$nupkgsFull', found $($candidateNupkgs.Count): $($candidateNupkgs.Name -join ', ')" +} + +$packedNupkgPath = $candidateNupkgs[0].FullName +$nupkgMatch = [System.Text.RegularExpressions.Regex]::Match($candidateNupkgs[0].Name, '^squad\.agents\.ai\.(?.+)\.nupkg$', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) +if (-not $nupkgMatch.Success) { + throw "Could not parse a package version out of nupkg filename '$($candidateNupkgs[0].Name)'." +} +$PackageVersion = $nupkgMatch.Groups['version'].Value +Write-Host "Detected packed Squad.Agents.AI version: $PackageVersion (from $($candidateNupkgs[0].Name))" + +$packedNupkgHash = Get-Sha512Base64 -FilePath $packedNupkgPath +Write-Host "Packed nupkg SHA512 (base64): $packedNupkgHash" + +$scratchDir = Join-Path ([System.IO.Path]::GetTempPath()) "squad-agents-ai-consumer-$([guid]::NewGuid())" +New-Item -ItemType Directory -Path $scratchDir | Out-Null +Write-Host "Scratch consumer project: $scratchDir" + +try { + # --- Scratch .csproj, built with System.Xml.Linq so PackageVersion (and any future + # dynamic value) is always escaped correctly regardless of its content. --- + $csprojPath = Join-Path $scratchDir "ConsumerCheck.csproj" + $xns = [System.Xml.Linq.XName] + $csprojRoot = [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('Project'), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('PropertyGroup'), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('TargetFramework'), $TargetFramework), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('OutputType'), 'Exe'), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('ImplicitUsings'), 'enable'), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('Nullable'), 'enable'), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('IsPackable'), 'false') + ), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('ItemGroup'), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('PackageReference'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('Include'), 'Squad.Agents.AI'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('Version'), $PackageVersion) + ) + ) + ) + $csprojRoot.SetAttributeValue($xns::op_Implicit('Sdk'), 'Microsoft.NET.Sdk') + [System.Xml.Linq.XDocument]::new($csprojRoot).Save($csprojPath) + + $programContent = 'System.Console.WriteLine("Squad.Agents.AI consumer packaging check placeholder.");' + Set-Content -Path (Join-Path $scratchDir "Program.cs") -Value $programContent -Encoding utf8 + + # --- Scratch nuget.config, also built with System.Xml.Linq. Two sources only: + # nuget.org and the local nupkgs folder this same job just produced. Package Source + # Mapping pins Squad.Agents.AI to ONLY the local source (an identically named/versioned + # package could otherwise exist, now or in future, on nuget.org and be silently + # substituted); everything else (Squad.Agents.AI's transitive dependencies, whatever + # they are, now or later) falls through the "*" catch-all to nuget.org, so this does + # not need to enumerate them and cannot silently miss a future one. --- + $nugetConfigPath = Join-Path $scratchDir "nuget.config" + $packageSources = [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('packageSources'), + [System.Xml.Linq.XElement]::new($xns::op_Implicit('clear')), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('add'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('key'), 'local-squad-agents-ai'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('value'), $nupkgsFull) + ), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('add'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('key'), 'nuget.org'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('value'), 'https://api.nuget.org/v3/index.json') + ) + ) + $packageSourceMapping = [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('packageSourceMapping'), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('packageSource'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('key'), 'local-squad-agents-ai'), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('package'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('pattern'), 'Squad.Agents.AI') + ) + ), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('packageSource'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('key'), 'nuget.org'), + [System.Xml.Linq.XElement]::new( + $xns::op_Implicit('package'), + [System.Xml.Linq.XAttribute]::new($xns::op_Implicit('pattern'), '*') + ) + ) + ) + $configRoot = [System.Xml.Linq.XElement]::new($xns::op_Implicit('configuration'), $packageSources, $packageSourceMapping) + [System.Xml.Linq.XDocument]::new($configRoot).Save($nugetConfigPath) + + Write-Section "Restoring scratch consumer (local nupkg + nuget.org only, via scratch nuget.config with Package Source Mapping)" + & dotnet restore $csprojPath + if ($LASTEXITCODE -ne 0) { + throw "Restore of scratch consumer project failed (exit code $LASTEXITCODE)." + } + + Write-Section "Proving Squad.Agents.AI was restored from the freshly packed local nupkg" + # Package Source Mapping above already restricts restore of Squad.Agents.AI to the + # local source, but this independently proves it by byte-for-byte hash: the NuGet + # global packages cache records the SHA512 of whatever nupkg it actually extracted, + # in a sibling .nupkg.sha512 file, for every restored package. + $globalPackagesOutput = dotnet nuget locals global-packages --list + $nugetPackagesRoot = ($globalPackagesOutput -split ':', 2)[1].Trim() + $restoredHashPath = Join-Path $nugetPackagesRoot "squad.agents.ai/$($PackageVersion.ToLowerInvariant())/squad.agents.ai.$($PackageVersion.ToLowerInvariant()).nupkg.sha512" + if (-not (Test-Path $restoredHashPath)) { + throw "Restored Squad.Agents.AI package hash file not found at '$restoredHashPath'. Cannot verify restore came from the local nupkg." + } + $restoredHash = (Get-Content -Raw -Path $restoredHashPath).Trim() + if ($restoredHash -ne $packedNupkgHash) { + throw "Restored Squad.Agents.AI package SHA512 ('$restoredHash') does not match the freshly packed local nupkg ('$packedNupkgHash'). This would mean the consumer resolved a DIFFERENT Squad.Agents.AI package than the one this job just built, defeating the purpose of this check." + } + Write-Host "Restored Squad.Agents.AI SHA512 matches the freshly packed local nupkg exactly." + + Write-Section "Building scratch consumer for the current runner's default RID" + & dotnet build $csprojPath -c Release --no-restore + if ($LASTEXITCODE -ne 0) { + throw "Build of scratch consumer project failed (exit code $LASTEXITCODE)." + } + + $outDir = Join-Path $scratchDir "bin/Release/$TargetFramework" + if (-not (Test-Path $outDir)) { + throw "Consumer build output directory '$outDir' does not exist." + } + + Write-Section "Inspecting consumer build output for the native Copilot payload" + $nativeRoot = Get-ChildItem -Path $outDir -Directory -Filter "runtimes" -ErrorAction SilentlyContinue + if (-not $nativeRoot) { + throw "No 'runtimes' directory found in consumer build output '$outDir'. The Copilot CLI native payload did not propagate to a real PackageReference consumer of Squad.Agents.AI." + } + + $nativeFiles = Get-ChildItem -Path $outDir -Recurse -File | + Where-Object { $_.FullName -match '[\\/]runtimes[\\/][^\\/]+[\\/]native[\\/]' } + + if (-not $nativeFiles -or $nativeFiles.Count -eq 0) { + throw "No files found under runtimes/*/native/ in consumer build output '$outDir'." + } + + Write-Host "Found native payload files:" + foreach ($f in $nativeFiles) { Write-Host " $($f.FullName)" } + + $cliBinary = $nativeFiles | Where-Object { $_.Name -in @('copilot', 'copilot.exe') } + if (-not $cliBinary) { + throw "Copilot CLI executable ('copilot' or 'copilot.exe') not found under runtimes/*/native/ in consumer output. Expected the payload from GitHub.Copilot.SDK / Copilot CLI $ExpectedCopilotCliVersion." + } + + $runtimeLibNames = @('copilot_runtime.dll', 'libcopilot_runtime.so', 'libcopilot_runtime.dylib', 'runtime.node') + $runtimeLib = $nativeFiles | Where-Object { $_.Name -in $runtimeLibNames } + if (-not $runtimeLib) { + throw "No runtime.node-derived native library ($($runtimeLibNames -join ', ')) found under runtimes/*/native/ in consumer output." + } + Write-Host "Native Copilot CLI executable: $($cliBinary.FullName)" + Write-Host "Native runtime.node-derived library: $($runtimeLib.FullName)" + + Write-Section "Proving the downloaded Copilot CLI version" + # The SDK's build/ targets cache the extracted CLI per-version at + # obj/{config}/{tfm}/copilot-cli/{CopilotCliVersion}/{platform}/..., so the directory + # names under copilot-cli/ are a direct, tamper-evident record of which CLI version(s) + # were actually downloaded for this consumer, independent of file content inspection, + # and without ever executing the binary itself. + $objDir = Join-Path $scratchDir "obj" + $copilotCliDirs = Get-ChildItem -Path $objDir -Recurse -Directory -Filter "copilot-cli" -ErrorAction SilentlyContinue + if (-not $copilotCliDirs) { + throw "No 'copilot-cli' cache directory found under '$objDir'. Cannot verify which Copilot CLI version was downloaded." + } + + $downloadedVersions = $copilotCliDirs | + ForEach-Object { Get-ChildItem -Path $_.FullName -Directory } | + Select-Object -ExpandProperty Name -Unique + + Write-Host "Copilot CLI version(s) downloaded for the consumer: $($downloadedVersions -join ', ')" + + if ($downloadedVersions -notcontains $ExpectedCopilotCliVersion) { + throw "Expected Copilot CLI version '$ExpectedCopilotCliVersion' was not downloaded for the consumer (found: $($downloadedVersions -join ', ')). The native-payload propagation fix did not take effect." + } + + foreach ($forbidden in $ForbiddenCopilotCliVersions) { + if ($downloadedVersions -contains $forbidden) { + throw "Forbidden stale Copilot CLI version '$forbidden' was downloaded for the consumer. This is the exact regression (Squad.Agents.AI PR #1519 / Reviewer finding #1) this check exists to catch: the Microsoft.Agents.AI.GitHub.Copilot buildTransitive bridge resolved an older GitHub.Copilot.SDK version instead of the one Squad.Agents.AI references." + } + } + + Write-Host "" + Write-Host "PASS: consumer resolved Copilot CLI $ExpectedCopilotCliVersion (not $($ForbiddenCopilotCliVersions -join ', ')); native executable and runtime library both present under runtimes/*/native/; Squad.Agents.AI restored from the freshly packed local nupkg (SHA512 verified)." -ForegroundColor Green +} +finally { + Remove-Item -Path $scratchDir -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/src/Squad.Agents.AI/README.md b/src/Squad.Agents.AI/README.md index 1b88a3552..76ef2847e 100644 --- a/src/Squad.Agents.AI/README.md +++ b/src/Squad.Agents.AI/README.md @@ -1,6 +1,6 @@ # Squad.Agents.AI -> **Preview package.** `Squad.Agents.AI` is a `0.5.1` preview NuGet package for early adopters. It multi-targets `net8.0`, `net9.0`, and `net10.0`, and depends on preview Microsoft Agent Framework / GitHub Copilot SDK packages, so APIs may change before a stable release. +> **Preview package.** `Squad.Agents.AI` is a preview NuGet package for early adopters. It multi-targets `net8.0`, `net9.0`, and `net10.0`, and depends on preview Microsoft Agent Framework / GitHub Copilot SDK packages, so APIs may change before a stable release. ## What it does diff --git a/src/Squad.Agents.AI/Squad.Agents.AI.csproj b/src/Squad.Agents.AI/Squad.Agents.AI.csproj index 0bc711898..ff82243c0 100644 --- a/src/Squad.Agents.AI/Squad.Agents.AI.csproj +++ b/src/Squad.Agents.AI/Squad.Agents.AI.csproj @@ -17,23 +17,41 @@ MIT README.md true - 0.5.5 + + 0.5.6-rc1 - + - + @@ -44,4 +62,75 @@ + + + + + + + + + + + + + + + + + <_CopilotSdkVersionBridgePropsPath>$(IntermediateOutputPath)Squad.Agents.AI.props + + + + + + +