Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/squad-agents-ai-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
64 changes: 64 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<Project>
<!--
Repo-wide MSBuild defaults for the Squad.Agents.AI .NET package (src/Squad.Agents.AI,
test/Squad.Agents.AI.Tests, src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample).
This file currently only exists to fix Copilot CLI native-payload propagation; see
below. It applies to every project in the repo, but today those three are the only
.csproj files that exist.
-->
<PropertyGroup>
<!--
Single source of truth for the GitHub.Copilot.SDK version Squad.Agents.AI targets.
Squad.Agents.AI.csproj's PackageReference uses this value, and the override right
below keeps the Microsoft.Agents.AI.GitHub.Copilot native-payload bridge in sync
with it, so bumping this one property is enough to move both together.
-->
<SquadCopilotSdkVersion>1.0.7</SquadCopilotSdkVersion>

<!--
Why this override is required:
Microsoft.Agents.AI.GitHub.Copilot ships GitHub.Copilot.SDK's CLI-download build/
targets to transitive consumers via a buildTransitive/ bridge file
(Microsoft.Agents.AI.GitHub.Copilot.targets), because GitHub.Copilot.SDK itself only
ships those targets under build/ (which NuGet auto-imports solely for projects with
a DIRECT PackageReference to it). That bridge file imports
"$(NuGetPackageRoot)/github.copilot.sdk/$(_MicrosoftAgentsAICopilotSdkVersion)/build/GitHub.Copilot.SDK.targets"
and, when $(_MicrosoftAgentsAICopilotSdkVersion) is unset, defaults it to the SDK
version the adapter itself was packed against, its nuspec's dependency FLOOR
(1.0.5 for Microsoft.Agents.AI.GitHub.Copilot 1.14.0-rc1), NOT the version this repo
actually depends on ($(SquadCopilotSdkVersion), i.e. 1.0.7). Left unset, any project
that pulls in the adapter WITHOUT a direct PackageReference to GitHub.Copilot.SDK
(test/Squad.Agents.AI.Tests, src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample here,
and any real external NuGet consumer of Squad.Agents.AI) downloads and bundles the
OLDER Copilot CLI build pinned to SDK 1.0.5 (Copilot CLI 1.0.67) instead of the CLI
pinned to SDK 1.0.7 (Copilot CLI 1.0.71); verified against this repo's own CI logs
for PR #1519 (run 29856026557): "Build package" (src/Squad.Agents.AI, which DOES have
the direct PackageReference) downloaded CLI 1.0.71, while "Build tests" and
"Build sample" (no direct PackageReference) downloaded CLI 1.0.67, on both
ubuntu-latest and windows-latest.

Setting the property here (Directory.Build.props is imported before NuGet's
per-project generated props/targets) makes every project in this repo resolve the
SAME SDK version, and therefore the same native Copilot CLI/runtime.node payload,
whether or not it has a direct PackageReference to GitHub.Copilot.SDK. This is the
exact override contract Microsoft.Agents.AI.GitHub.Copilot.targets documents in its
own comments ("consumers may override the SDK version path by setting
$(_MicrosoftAgentsAICopilotSdkVersion) before this file is imported"), so it works
regardless of NuGet's props/targets import ordering between packages: if this file's
value is seen first, the adapter's own conditional default (guarded by
Condition="'$(_MicrosoftAgentsAICopilotSdkVersion)' == ''") never fires; if it is
seen after, Squad.Agents.AI's own packed buildTransitive/Squad.Agents.AI.props
(generated at pack time from this same property, see Squad.Agents.AI.csproj)
unconditionally overwrites it again for real external consumers.

Excluded for the Squad.Agents.AI project itself: it already gets the right SDK
version via its own direct PackageReference to GitHub.Copilot.SDK (whose build/
import happens unconditionally, ahead of the adapter's fallback), so setting the
override there too would just make NuGet import the SAME
github.copilot.sdk/$(SquadCopilotSdkVersion)/build/GitHub.Copilot.SDK.targets file
twice, and MSBuild warns (MSB4011) about the resulting harmless-but-noisy duplicate
import.
-->
<_MicrosoftAgentsAICopilotSdkVersion Condition="'$(MSBuildProjectName)' != 'Squad.Agents.AI'">$(SquadCopilotSdkVersion)</_MicrosoftAgentsAICopilotSdkVersion>
</PropertyGroup>
</Project>
Loading
Loading