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
+
+
+
+
+
+
+