Repository navigation
fix(macos): resolve native menus and verify lifecycle transitions #164
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
| # Rehearsal of the release build. It is its own workflow, not a mode of release.yml, | |
| # because a workflow that checks out a ref chosen at dispatch time while running on the | |
| # default branch, and shares caches with other runs, can have those caches poisoned | |
| # (CodeQL actions/cache-poisoning/poisonable-step). No runtime condition removes that. | |
| # So this one holds read-only contents permission, uses no caches, and reaches no | |
| # publishing job. | |
| name: Release Dry Run | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: Semver version to name the archives with, for example 0.9.0 | |
| required: true | |
| type: string | |
| pull_request: | |
| paths: | |
| - '.github/workflows/release.yml' | |
| - '.github/workflows/release-dry-run.yml' | |
| - 'npm/**' | |
| - 'crates/ffi/Cargo.toml' | |
| - 'crates/ffi/src/**' | |
| - 'crates/ffi/include/**' | |
| - 'crates/linux/src/input/clipboard/**' | |
| - 'crates/linux/src/bin/**' | |
| - 'scripts/check-elf-glibc-floor.sh' | |
| - 'scripts/check-elf-glibc-floor-selftest.sh' | |
| - 'scripts/check-linux-archive-contents.sh' | |
| - 'scripts/ffi-archive-readme-linux.md' | |
| - 'scripts/private-session.sh' | |
| - 'tests/linux-dist/**' | |
| - 'tests/ffi-python/**' | |
| - 'scripts/ci-ffi-helper-discovery-smoke.sh' | |
| permissions: {} | |
| concurrency: | |
| group: release-dry-run-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| cli: | |
| name: Dry-run CLI (${{ matrix.target }}) | |
| runs-on: ${{ matrix.runner }} | |
| container: ${{ matrix.container }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| env: | |
| GLIBC_FLOOR: '2.35' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: aarch64-apple-darwin | |
| runner: macos-latest | |
| - target: x86_64-apple-darwin | |
| runner: macos-latest | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-24.04 | |
| container: ubuntu:22.04@sha256:0a9887931b0e38af2e3487ed39e29436b0535c4f3f52e57745baaf79d1898956 | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-24.04-arm | |
| container: ubuntu:22.04@sha256:0a9887931b0e38af2e3487ed39e29436b0535c4f3f52e57745baaf79d1898956 | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| - target: aarch64-pc-windows-msvc | |
| runner: windows-11-arm | |
| steps: | |
| - name: Install container build tools | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| apt-get update | |
| apt-get install -y --no-install-recommends ca-certificates curl build-essential git xz-utils | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install rustup (container) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$(uname -m)" in | |
| x86_64) TRIPLE=x86_64-unknown-linux-gnu ;; | |
| aarch64) TRIPLE=aarch64-unknown-linux-gnu ;; | |
| esac | |
| BASE="https://static.rust-lang.org/rustup/dist/${TRIPLE}" | |
| curl --proto '=https' --tlsv1.2 -fsSLO "${BASE}/rustup-init" | |
| echo "$(curl --proto '=https' --tlsv1.2 -fsSL "${BASE}/rustup-init.sha256" | cut -d' ' -f1) rustup-init" | sha256sum -c - | |
| chmod +x rustup-init | |
| ./rustup-init -y --default-toolchain none --profile minimal | |
| echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" | |
| "$HOME/.cargo/bin/rustup" toolchain install | |
| - name: Install Rust toolchain | |
| run: | | |
| rustup show | |
| rustup target add ${{ matrix.target }} | |
| - name: Build release binary (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| cargo build --locked --release -p agent-desktop --target ${{ matrix.target }} | |
| cargo build --locked --release -p agent-desktop-macos --bin agent-desktop-macos-helper --target ${{ matrix.target }} | |
| - name: Build release binary (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| cargo build --locked --release -p agent-desktop --target ${{ matrix.target }} | |
| cargo build --locked --release -p agent-desktop-linux --bins --target ${{ matrix.target }} | |
| - name: Build release binary (Windows) | |
| if: runner.os == 'Windows' | |
| run: cargo build --locked --release -p agent-desktop --target ${{ matrix.target }} | |
| - name: Check glibc floor and size (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| LIMIT=$((15 * 1024 * 1024)) | |
| for FILE in agent-desktop agent-desktop-linux-clipboard-owner; do | |
| PATH_TO="target/${{ matrix.target }}/release/${FILE}" | |
| scripts/check-elf-glibc-floor.sh "${PATH_TO}" "${GLIBC_FLOOR}" | |
| SIZE=$(stat -c%s "${PATH_TO}") | |
| echo "${FILE} size: ${SIZE} bytes" | |
| if [ "$SIZE" -gt "$LIMIT" ]; then | |
| echo "FAIL: shipped executable ${FILE} exceeds 15MB limit (${SIZE} bytes)" | |
| exit 1 | |
| fi | |
| done | |
| - name: Smoke-run the built binary (macOS) | |
| if: runner.os == 'macOS' | |
| run: target/${{ matrix.target }}/release/agent-desktop version | |
| - name: Smoke-run the built binary (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: target/${{ matrix.target }}/release/agent-desktop version | |
| - name: Smoke-run the built binary (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: '& "target/${{ matrix.target }}/release/agent-desktop.exe" version' | |
| - name: Create tarball (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| VERSION: ${{ inputs.version || format('0.0.0-pr{0}', github.event.pull_request.number) }} | |
| run: | | |
| TARBALL="agent-desktop-v${VERSION}-${{ matrix.target }}.tar.gz" | |
| tar -czf "${TARBALL}" -C "target/${{ matrix.target }}/release" \ | |
| agent-desktop agent-desktop-macos-helper | |
| tar -tzf "${TARBALL}" | sort > listing.txt | |
| printf 'agent-desktop\nagent-desktop-macos-helper\n' | sort > expected.txt | |
| diff -u expected.txt listing.txt | |
| shasum -a 256 "${TARBALL}" > "${TARBALL}.sha256" | |
| - name: Create tarball (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| VERSION: ${{ inputs.version || format('0.0.0-pr{0}', github.event.pull_request.number) }} | |
| run: | | |
| TARBALL="agent-desktop-v${VERSION}-${{ matrix.target }}.tar.gz" | |
| tar -czf "${TARBALL}" -C "target/${{ matrix.target }}/release" \ | |
| agent-desktop agent-desktop-linux-clipboard-owner | |
| scripts/check-linux-archive-contents.sh cli "${TARBALL}" | |
| sha256sum "${TARBALL}" > "${TARBALL}.sha256" | |
| - name: Create tarball (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| VERSION: ${{ inputs.version || format('0.0.0-pr{0}', github.event.pull_request.number) }} | |
| run: | | |
| $tarball = "agent-desktop-v${env:VERSION}-${{ matrix.target }}.tar.gz" | |
| & tar -czf $tarball -C "target/${{ matrix.target }}/release" agent-desktop.exe | |
| $listing = @(@(& tar -tzf $tarball) | Where-Object { $_ -ne '' }) | |
| if (@($listing).Count -ne 1 -or $listing[0] -ne 'agent-desktop.exe') { | |
| Write-Host "FAIL: archive entries are '$($listing -join ", ")', expected exactly agent-desktop.exe" | |
| exit 1 | |
| } | |
| $hash = (Get-FileHash $tarball -Algorithm SHA256).Hash.ToLower() | |
| "$hash $tarball" | Out-File -Encoding ascii "${tarball}.sha256" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: dry-run-binary-${{ matrix.target }} | |
| path: | | |
| agent-desktop-v*.tar.gz | |
| agent-desktop-v*.tar.gz.sha256 | |
| retention-days: 1 | |
| ffi: | |
| name: Dry-run FFI (${{ matrix.target }}) | |
| runs-on: ${{ matrix.runner }} | |
| container: ${{ matrix.container }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| env: | |
| GLIBC_FLOOR: '2.35' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-24.04 | |
| container: ubuntu:22.04@sha256:0a9887931b0e38af2e3487ed39e29436b0535c4f3f52e57745baaf79d1898956 | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-24.04-arm | |
| container: ubuntu:22.04@sha256:0a9887931b0e38af2e3487ed39e29436b0535c4f3f52e57745baaf79d1898956 | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| - target: aarch64-pc-windows-msvc | |
| runner: windows-11-arm | |
| steps: | |
| - name: Install container build tools | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| apt-get update | |
| apt-get install -y --no-install-recommends ca-certificates curl build-essential git xz-utils | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install rustup (container) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$(uname -m)" in | |
| x86_64) TRIPLE=x86_64-unknown-linux-gnu ;; | |
| aarch64) TRIPLE=aarch64-unknown-linux-gnu ;; | |
| esac | |
| BASE="https://static.rust-lang.org/rustup/dist/${TRIPLE}" | |
| curl --proto '=https' --tlsv1.2 -fsSLO "${BASE}/rustup-init" | |
| echo "$(curl --proto '=https' --tlsv1.2 -fsSL "${BASE}/rustup-init.sha256" | cut -d' ' -f1) rustup-init" | sha256sum -c - | |
| chmod +x rustup-init | |
| ./rustup-init -y --default-toolchain none --profile minimal | |
| echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" | |
| "$HOME/.cargo/bin/rustup" toolchain install | |
| - name: Install Rust toolchain | |
| run: | | |
| rustup show | |
| rustup target add ${{ matrix.target }} | |
| - name: Build FFI cdylib | |
| run: > | |
| cargo build --locked --profile release-ffi | |
| -p agent-desktop-ffi --target ${{ matrix.target }} | |
| - name: Build Linux clipboard owner for FFI consumers | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: cargo build --locked --release -p agent-desktop-linux --bins --target ${{ matrix.target }} | |
| - name: Check glibc floor (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| scripts/check-elf-glibc-floor.sh "target/${{ matrix.target }}/release-ffi/libagent_desktop_ffi.so" "${GLIBC_FLOOR}" | |
| scripts/check-elf-glibc-floor.sh "target/${{ matrix.target }}/release/agent-desktop-linux-clipboard-owner" "${GLIBC_FLOOR}" | |
| - name: Stage and verify archive contents (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| VERSION: ${{ inputs.version || format('0.0.0-pr{0}', github.event.pull_request.number) }} | |
| run: | | |
| STAGE="agent-desktop-ffi-v${VERSION}-${{ matrix.target }}" | |
| mkdir -p "$STAGE/lib" "$STAGE/include" | |
| cp "target/${{ matrix.target }}/release-ffi/libagent_desktop_ffi.so" "$STAGE/lib/" | |
| cp "target/${{ matrix.target }}/release/agent-desktop-linux-clipboard-owner" "$STAGE/lib/" | |
| chmod +x "$STAGE/lib/agent-desktop-linux-clipboard-owner" | |
| cp crates/ffi/include/agent_desktop.h "$STAGE/include/" | |
| cp LICENSE "$STAGE/" | |
| cat > "$STAGE/README.md" <<EOF | |
| # agent-desktop FFI cdylib — v${VERSION} (${{ matrix.target }}) | |
| - \`lib/libagent_desktop_ffi.so\` — shared library | |
| - \`include/agent_desktop.h\` — committed C ABI header | |
| - \`LICENSE\` — Apache-2.0 | |
| Docs: https://github.com/vercel-labs/agent-desktop/tree/main/skills/agent-desktop-ffi | |
| EOF | |
| cat scripts/ffi-archive-readme-linux.md >> "$STAGE/README.md" | |
| # shellcheck disable=SC2016 | |
| echo '- `lib/agent-desktop-linux-clipboard-owner` — clipboard owner helper, colocated for library-image discovery' >> "$STAGE/README.md" | |
| (cd "$STAGE" && sha256sum lib/* include/* LICENSE > SHA256SUMS) | |
| tar -czf "${STAGE}.tar.gz" "${STAGE}" | |
| (cd "$STAGE" && sha256sum -c SHA256SUMS) | |
| scripts/check-linux-archive-contents.sh ffi "${STAGE}.tar.gz" | |
| sha256sum "${STAGE}.tar.gz" > "${STAGE}.tar.gz.sha256" | |
| - name: Stage and verify archive contents (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| VERSION: ${{ inputs.version || format('0.0.0-pr{0}', github.event.pull_request.number) }} | |
| run: | | |
| $stage = "agent-desktop-ffi-v${env:VERSION}-${{ matrix.target }}" | |
| New-Item -ItemType Directory -Path "$stage/lib", "$stage/include" | Out-Null | |
| $out = "target/${{ matrix.target }}/release-ffi" | |
| Copy-Item "$out/agent_desktop_ffi.dll" "$stage/lib/" | |
| Copy-Item "$out/agent_desktop_ffi.dll.lib" "$stage/lib/" | |
| Copy-Item "crates/ffi/include/agent_desktop.h" "$stage/include/" | |
| foreach ($required in @('agent_desktop_ffi.dll', 'agent_desktop_ffi.dll.lib')) { | |
| if (-not (Test-Path "$stage/lib/$required")) { | |
| Write-Host "FAIL: $required was not staged into $stage/lib" | |
| exit 1 | |
| } | |
| } | |
| Compress-Archive -Path $stage -DestinationPath "$stage.zip" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: dry-run-ffi-${{ matrix.target }} | |
| path: | | |
| agent-desktop-ffi-v*.zip | |
| agent-desktop-ffi-v*.tar.gz | |
| agent-desktop-ffi-v*.tar.gz.sha256 | |
| retention-days: 1 | |
| linux-archives: | |
| name: Linux archive contents (${{ matrix.target }}) | |
| needs: [cli, ffi] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| npm_key: linux-x64 | |
| - target: aarch64-unknown-linux-gnu | |
| npm_key: linux-arm64 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Download this target's CLI and FFI archives | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: "dry-run-{binary,ffi}-${{ matrix.target }}" | |
| merge-multiple: true | |
| path: archives | |
| - name: Check the helper is in all three channels | |
| run: | | |
| scripts/check-linux-archive-contents.sh all \ | |
| archives/agent-desktop-v*-${{ matrix.target }}.tar.gz \ | |
| archives/agent-desktop-ffi-v*-${{ matrix.target }}.tar.gz \ | |
| ${{ matrix.npm_key }} | |
| linux-consumer: | |
| defaults: | |
| run: | |
| shell: bash | |
| name: Linux consumer (${{ matrix.target }}) | |
| needs: [cli, ffi] | |
| if: ${{ !cancelled() }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-24.04 | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '24' | |
| - name: Download this architecture's CLI and FFI archives | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: "dry-run-{binary,ffi}-${{ matrix.target }}" | |
| merge-multiple: true | |
| path: archives | |
| - name: Extract both archives into clean directories | |
| run: | | |
| set -euo pipefail | |
| for KIND in agent-desktop-v agent-desktop-ffi-v; do | |
| ARCHIVE=$(ls archives/${KIND}*-${{ matrix.target }}.tar.gz) | |
| DIR="extracted/${KIND}" | |
| mkdir -p "$DIR" | |
| tar -xzf "$ARCHIVE" -C "$DIR" | |
| done | |
| find extracted -maxdepth 3 | sort | |
| - name: Install the session and observer packages | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| at-spi2-core dbus-x11 xvfb x11-utils openbox gnome-calculator \ | |
| python3-gi gir1.2-atspi-2.0 gir1.2-gtk-3.0 gcc | |
| - name: Deterministic packaged owner contracts | |
| run: | | |
| python3 -I -m unittest discover -s tests/ffi-python -p 'test_linux_helper.py' | |
| python3 -I -m unittest discover -s tests/linux-dist -p 'test_*.py' | |
| - name: FFI library-adjacent helper discovery from this archive | |
| run: | | |
| set -euo pipefail | |
| FFI_DIR=$(ls -d extracted/agent-desktop-ffi-v/agent-desktop-ffi-v*) | |
| mkdir -p target/release-ffi target/release | |
| cp "$FFI_DIR/lib/libagent_desktop_ffi.so" target/release-ffi/ | |
| cp "$FFI_DIR/lib/agent-desktop-linux-clipboard-owner" target/release/ | |
| AGENT_DESKTOP_WM_ROOT=/ scripts/private-session.sh --wm -- \ | |
| scripts/ci-ffi-helper-discovery-smoke.sh | |
| - name: Real C consumer against the extracted FFI archive | |
| run: | | |
| AGENT_DESKTOP_WM_ROOT=/ scripts/private-session.sh --wm -- \ | |
| tests/linux-dist/run-consumer.sh "$(ls -d extracted/agent-desktop-ffi-v/agent-desktop-ffi-v*)" | |
| - name: Packed npm install from the extracted CLI archive | |
| run: | | |
| AGENT_DESKTOP_WM_ROOT=/ scripts/private-session.sh --wm -- \ | |
| tests/linux-dist/run-npm-install.sh "archives" |