Build and Release AgentBridge #81
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release AgentBridge | |
| # The release is now AUTOMATIC: pushing master with IsPrerelease=false in AgentBridge.csproj | |
| # (in the pushed commit) triggers the wait for the dependency NuGet packages, the build of the | |
| # 5 platform archives and the GitHub release — the tag v1.yy.MM.dd is created on the fly. No | |
| # tag push and no release.ps1 invocation are needed anymore. With IsPrerelease=true, or when | |
| # today's tag already exists, the run is skipped. See docs-dev/RELEASING.md. | |
| on: | |
| push: | |
| branches: [master] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| # Serializes runs of this workflow per branch: with cancel-in-progress: false a NEW run (the | |
| # gate-restore push, a manual push during the build) never cancels the QUEUED or RUNNING | |
| # release run. The back-to-back double push of release.ps1 raced GitHub's Actions queue on | |
| # 2026-08-26: the runs were created in inverted order and the gate-off (release) run was | |
| # failed while still queued — no release was produced. The restore commit carries [skip ci] | |
| # (release.ps1) so it creates no run at all; this group is the structural guard for every | |
| # other quick double push. | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| # Release gate: when IsPrerelease=true in AgentBridge.csproj the version carries a | |
| # "-prerelease" suffix and no GitHub release is created. See AGENTS.md. | |
| check-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.ver.outputs.version }} | |
| do_release: ${{ steps.gate.outputs.do_release }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| path: AgentBridge | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Detect version + release gate | |
| id: ver | |
| shell: bash | |
| run: | | |
| # -getProperty:Version prints the raw value (single property) — grep handles both | |
| # raw ("1.26.08.09") and JSON ({"Properties":{"Version":"..."}}) output shapes. | |
| VER=$(dotnet msbuild AgentBridge/AgentBridge.csproj -getProperty:Version -nologo | grep -oE '[0-9]+(\.[0-9]+)*(-[a-z0-9]+)?' | head -n1) | |
| echo "version=$VER" | |
| case "$VER" in *-prerelease*) echo "prerelease=true" ;; *) echo "prerelease=false" ;; esac | |
| echo "prerelease=$(case "$VER" in *-prerelease*) echo true ;; *) echo false ;; esac)" >> "$GITHUB_OUTPUT" | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| - name: Decide whether to release | |
| id: gate | |
| shell: bash | |
| # The checkout above uses path: AgentBridge, so git must run from inside the repo | |
| # (the workspace root is not a git repository — 'origin' does not exist there). | |
| working-directory: AgentBridge | |
| env: | |
| VERSION: ${{ steps.ver.outputs.version }} | |
| PRERELEASE: ${{ steps.ver.outputs.prerelease }} | |
| run: | | |
| if [ "$PRERELEASE" = "true" ]; then | |
| echo "IsPrerelease=true -> prerelease build, no GitHub release" | |
| echo "do_release=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q .; then | |
| echo "Tag v$VERSION already exists -> no second release for this version" | |
| echo "do_release=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "do_release=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| build: | |
| needs: check-version | |
| if: needs.check-version.outputs.do_release == 'true' | |
| name: Build ${{ matrix.rid }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| rid: win-x64 | |
| ext: .exe | |
| - os: ubuntu-latest | |
| rid: linux-x64 | |
| ext: '' | |
| # linux-arm64: supported since KokoroSharp 0.8.4 (phonemizer is the pure-managed | |
| # MisakiSharp — no espeak-ng binary needed) + the native Microsoft.ML.OnnxRuntime | |
| # package ships libonnxruntime.so for linux-arm64. Cross-compiled from the x64 | |
| # runner (pure managed code + RID-specific NuGet assets). | |
| - os: ubuntu-latest | |
| rid: linux-arm64 | |
| ext: '' | |
| - os: macos-latest | |
| rid: osx-x64 | |
| ext: '' | |
| - os: macos-latest | |
| rid: osx-arm64 | |
| ext: '' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| path: AgentBridge | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| # The dependency NuGet packages are date-versioned (1.yy.MM.dd) and published by each | |
| # repo's publish.yml when sync-all.ps1 pushed them; nuget.org propagation is not | |
| # instant (official figures: up to 30 minutes). A GLOBAL 30-minute window from the start | |
| # of the wait: each cycle checks ALL packages and ends as soon as every one is visible; | |
| # after the window, packages still missing (only changed repos publish today) are | |
| # reported with a warning and the build proceeds — the floating 1.* restore picks the | |
| # latest available version, which for an unchanged repo is identical to today's version. | |
| # The list is ONLY the packages the build actually restores (the Graphene.AIOrchestrator | |
| # closure): the tool plugins (DocumentTool, SpreadsheetTool, OfficeTool, | |
| # PresentationTool, OfficeSupportTool) are NOT build dependencies — they are fetched as | |
| # self-contained zips from their own GitHub Releases below, never from NuGet. | |
| - name: Wait for dependency packages on NuGet | |
| shell: bash | |
| env: | |
| VERSION: ${{ needs.check-version.outputs.version }} | |
| run: | | |
| NVER=$(printf '%s' "$VERSION" | awk -F. '{for(i=1;i<=NF;i++){gsub(/^0+/,"",$i); if($i=="")$i=0; printf "%s%s",(i>1?".":""),$i} print ""}') | |
| echo "Waiting for dependency packages at version $NVER ..." | |
| for i in $(seq 1 60); do | |
| missing="" | |
| for pkg in graphene.aiorchestrator alltomarkdown mermaidrendering graphene.reversemarkdown uisupportgeneric; do | |
| if curl -fsS "https://api.nuget.org/v3-flatcontainer/$pkg/index.json" | grep -q "\"$NVER\""; then | |
| echo "$pkg $NVER available (attempt $i)" | |
| else | |
| missing="$missing $pkg" | |
| fi | |
| done | |
| if [ -z "$missing" ]; then break; fi | |
| sleep 30 | |
| done | |
| if [ -n "$missing" ]; then | |
| echo "::warning::after 30 min these packages are not yet at $NVER; proceeding with the latest available version (no publish for these repos today):$missing" | |
| fi | |
| # Builds against the published NuGet packages (Graphene.AIOrchestrator 1.* and its | |
| # dependencies) — the sibling repos are private and never checked out here. | |
| - name: Publish | |
| shell: bash | |
| run: | | |
| dotnet publish AgentBridge/AgentBridge.csproj -c Release -r ${{ matrix.rid }} \ | |
| --self-contained true \ | |
| -p:PublishSingleFile=true \ | |
| -p:IncludeNativeLibrariesForSelfExtract=true \ | |
| -p:DebugType=None -p:DebugSymbols=false \ | |
| -o ./publish | |
| # The archive deliberately ships kokoro.onnx (325 MB) plus the KokoroSharp | |
| # voices/ + voices-zh/ content: single-file publish bundles managed code only, | |
| # and the TTS endpoints need those assets next to the exe. The native | |
| # onnxruntime engine is included per-RID by the Microsoft.ML.OnnxRuntime | |
| # package (libonnxruntime.so / onnxruntime.dll). | |
| # | |
| # Tool plugins (Graphene.DocumentTool, Graphene.SpreadsheetTool, Graphene.OfficeTool, | |
| # Graphene.PresentationTool, Graphene.OfficeSupportTool) are loaded DYNAMICALLY from | |
| # Tools/ by ToolPluginHost (byte-loaded, never referenced): the single-file publish | |
| # cannot bundle them. Fetch each plugin's self-contained release zip (plugin + non-host | |
| # deps, minus the AIOrchestrator graph — produced by the plugin repos' standard | |
| # plugin-release.yml) from its PUBLIC GitHub release and ship it into publish/Tools/<Plugin>/. | |
| # Each payload's assets/ (OfficeSupportTool templates, PresentationTool bg/js, icons) is | |
| # merged into publish/assets/ — the plugins resolve host-level assets from | |
| # AppContext.BaseDirectory\assets (same convention as the ShipWithAssets dev target and | |
| # PluginUpdater.MergeHostAssets at update time). NuGet is not part of the plugin | |
| # deployment channel. | |
| - name: Fetch tool plugins into Tools/ | |
| shell: bash | |
| run: | | |
| mkdir -p /tmp/plugins publish/Tools publish/assets | |
| for tool in DocumentTool SpreadsheetTool OfficeTool PresentationTool OfficeSupportTool; do | |
| # Latest release tag via the /releases/latest redirect (no API call, no rate limit). | |
| tag=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/Graphene-Lab/$tool/releases/latest" \ | |
| | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+[^/]*$' | head -n1) | |
| if [ -z "$tag" ]; then | |
| echo "::warning::$tool has no GitHub release yet — tool will be missing from Tools/" | |
| continue | |
| fi | |
| ver="${tag#v}" | |
| zip="$tool-$ver.zip" | |
| if ! curl -fsSL -o "/tmp/plugins/$zip" "https://github.com/Graphene-Lab/$tool/releases/download/$tag/$zip"; then | |
| echo "::warning::cannot download $zip for $tool — tool will be missing from Tools/" | |
| continue | |
| fi | |
| (cd /tmp/plugins && unzip -qo "$zip") | |
| mkdir -p "publish/Tools/$tool" | |
| cp -r "/tmp/plugins/$tool/." "publish/Tools/$tool/" | |
| if [ -d "/tmp/plugins/$tool/assets" ]; then | |
| cp -r "/tmp/plugins/$tool/assets/." "publish/assets/" | |
| fi | |
| done | |
| find publish/Tools -type f 2>/dev/null | sort || true | |
| - name: Package | |
| shell: bash | |
| run: | | |
| cd publish | |
| tar -czf ../agentbridge-${{ matrix.rid }}.tar.gz . | |
| cd .. | |
| ls -lh agentbridge-${{ matrix.rid }}.tar.gz | |
| - name: Upload archive | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: agentbridge-${{ matrix.rid }} | |
| path: agentbridge-${{ matrix.rid }}.tar.gz | |
| release: | |
| needs: [check-version, build] | |
| if: needs.check-version.outputs.do_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download archives | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| # Pin the tag to the commit that triggered this run (github.sha = the gate-off commit) so | |
| # a later master push (e.g. the IsPrerelease restore after a button release) cannot move | |
| # it. The GITHUB_TOKEN push does not re-trigger workflows. The tag is informational: | |
| # nothing listens on it. | |
| - name: Tag the triggering commit | |
| shell: bash | |
| run: | | |
| git tag "v${{ needs.check-version.outputs.version }}" "$GITHUB_SHA" | |
| git push origin "v${{ needs.check-version.outputs.version }}" | |
| # Creates the GitHub release (the tag already exists) with all 5 platform archives. | |
| - name: Create GitHub Release v${{ needs.check-version.outputs.version }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ needs.check-version.outputs.version }} | |
| files: artifacts/**/*.tar.gz |