Skip to content

fix: make presentations available in multi-files and document office-… #235

fix: make presentations available in multi-files and document office-…

fix: make presentations available in multi-files and document office-… #235

Workflow file for this run

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
# DO NOT add a "workflows" key here — it is NOT a valid value in a workflow's permissions
# block (only PATs / GitHub Apps have it). Doing so makes the whole file invalid and every
# run fails with 0 jobs ("Invalid workflow file ... Unexpected value 'workflows'",
# 2026-09-04). If the tag-pin push is ever refused for a workflow-file change, pin the tag
# to a commit whose .github/workflows matches the default branch instead.
# 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 }}
need_nuget_wait: ${{ steps.nuget.outputs.need_nuget_wait }}
wait_packages: ${{ steps.nuget.outputs.wait_packages }}
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"
# IsPrerelease is the source of truth for the gate (the "-prerelease" suffix derives
# from it in the csproj). Read the property directly: if Version ever fails to carry
# a full date (e.g. an empty <ReleaseDate> makes the grep above extract a bare "1"),
# the suffix-based check alone would silently treat a prerelease push as a release
# and run the whole build for a garbage version (2026-09-04 incident).
PREREL=$(dotnet msbuild AgentBridge/AgentBridge.csproj -getProperty:IsPrerelease -nologo | grep -oE 'true|false' | head -n1)
echo "prerelease=${PREREL:-false}" >> "$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
# NuGetWait marker (set by release.ps1 from its core-repo pre-check): false means no
# core repo changed since its last tag → the 30-min dependency wait is skipped entirely
# (release.ps1 aborts the release if a changed core repo has no today-tag, so a missing
# marker can never silently ship a stale engine).
- name: Read NuGet wait marker
id: nuget
shell: bash
run: |
WAIT=$(dotnet msbuild AgentBridge/AgentBridge.csproj -getProperty:NuGetWait -nologo | grep -oE 'true|false' | head -n1)
echo "need_nuget_wait=${WAIT:-true}" >> "$GITHUB_OUTPUT"
# release.ps1 narrows the list to the packages whose repos actually changed today,
# so the wait below exits as soon as THEY propagate (repos that did not change never
# publish today's version and must not hold the wait). Default = the full set.
PKGS=$(dotnet msbuild AgentBridge/AgentBridge.csproj -getProperty:NuGetWaitPackages -nologo | head -n1)
echo "wait_packages=${PKGS:-graphene.aiorchestrator,alltomarkdown,mermaidrendering,graphene.reversemarkdown,uisupportgeneric,systemextra}" >> "$GITHUB_OUTPUT"
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 a v* tag is pushed; nuget.org propagation is not instant
# (official figures: up to 30 minutes). The wait runs ONLY when release.ps1 detected a
# changed core repo whose today's package is still propagating (<NuGetWait> marker):
# with no core change the wait would burn 30 minutes for packages that will never exist.
# release.ps1 ALSO narrows the list (NuGetWaitPackages) to the packages of the repos
# that changed today, so the window ends as soon as THEY become visible — repos that
# did not change never publish today's version and would otherwise hold the loop for the
# full 30 minutes. After the window, a still-missing package is a HARD FAILURE: the wait
# list only contains packages that SHOULD be at today's version, so expiry means that
# repo's publish.yml run failed or nuget.org exceeded its documented window — proceeding
# would restore YESTERDAY's engine and either crash the build with misleading errors
# (2026-09-06: "CrashReporter does not exist" after the AIOrchestrator publish died
# waiting for uisupportgeneric) or, worse, ship it silently. Fix the failed publish and
# re-run release.ps1 — the wait is skipped as soon as the package is visible.
# The list is ONLY the packages the build actually restores (the Graphene.AIOrchestrator
# closure + SystemExtra): 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
if: needs.check-version.outputs.need_nuget_wait == 'true'
shell: bash
env:
VERSION: ${{ needs.check-version.outputs.version }}
WAIT_PACKAGES: ${{ needs.check-version.outputs.wait_packages }}
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 $(printf '%s' "$WAIT_PACKAGES" | tr ',' ' '); do
if curl -fsS --max-time 20 "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 "::error::after 30 min these packages are still not at $NVER:$missing"
echo "::error::this release REQUIRES today's version of every package in <NuGetWaitPackages> — release.ps1 narrows it to the repos that changed today. A missing package means that repo's 'Publish NuGet Package' run failed or nuget.org propagation exceeded its documented window; proceeding would restore yesterday's engine (misleading build crash or silent stale release, 2026-09-06). Check the failed publish run of the repo above, fix it, then re-run release.ps1 — the wait is skipped as soon as the package is visible."
exit 1
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 PodcastTool; 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 || true)
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
# The host always ships the ONNX Runtime engine (per-RID GPU build); a plugin's
# transitive CPU onnxruntime must never shadow it (version conflicts, CPU-only
# QwenTTS) — drop the plugin copies here (same rule as ShipOnePlugin.targets).
find publish/Tools -name 'onnxruntime*' -delete
find publish/Tools -name 'libonnxruntime*' -delete
find publish/Tools -name 'Microsoft.ML.OnnxRuntime*.dll' -delete
find publish/Tools -type f 2>/dev/null | sort || true
# Windows voice bridge: fetch the AIOffice.VoiceAgent.Win self-contained win-x64 zip
# from its OWN GitHub release (public repo, same channel as the tool plugins) into
# publish/voiceagent/ — POST /v1/voice/listen spawns AIOffice.VoiceAgent.Win.exe from
# there. The local csproj target CopyVoiceAgentOutput covers developer machines that
# have the sibling VoiceAgent build; CI has no sibling, so this step is what makes the
# released Windows archive work out of the box. The zip contains a top-level
# AIOffice.VoiceAgent.Win/ folder (exe + voices/ + kokoro.onnx + native runtimes).
# Other RIDs do not ship voice recognition (Windows-only feature).
- name: Fetch VoiceAgent.Win into voiceagent/
if: matrix.rid == 'win-x64'
shell: bash
run: |
tag=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/Graphene-Lab/AIOffice.VoiceAgent.Win/releases/latest" \
| grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+[^/]*$' | head -n1 || true)
if [ -z "$tag" ]; then
echo "::warning::AIOffice.VoiceAgent.Win has no release yet — /voice will be unavailable in this archive"
exit 0
fi
ver="${tag#v}"
zip="AIOffice.VoiceAgent.Win-$ver.zip"
mkdir -p /tmp/voiceagent
if ! curl -fsSL -o "/tmp/voiceagent/$zip" "https://github.com/Graphene-Lab/AIOffice.VoiceAgent.Win/releases/download/$tag/$zip"; then
echo "::warning::cannot download $zip — /voice will be unavailable in this archive"
exit 0
fi
(cd /tmp/voiceagent && unzip -qo "$zip")
mkdir -p publish/voiceagent
cp -r /tmp/voiceagent/AIOffice.VoiceAgent.Win/. publish/voiceagent/
test -f publish/voiceagent/AIOffice.VoiceAgent.Win.exe \
&& echo "voiceagent payload ready: $(du -sh publish/voiceagent | cut -f1)" \
|| echo "::warning::voiceagent/ does not contain AIOffice.VoiceAgent.Win.exe"
- 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
# Windows Store MSI: separate job (not inside the win-x64 matrix build) so an
# installer/WiX problem can never block the archives + GitHub release — and
# continue-on-error keeps the run green when the MSI step fails (the MSI is an
# extra delivery channel; the release itself must not depend on it). Payload =
# the win-x64 archive the build already produced (single source of truth: the
# exact tree that ships). The Store submission (store-submit job below) points
# Partner Center at the VPS streaming proxy URL (see tools/store).
store-msi:
needs: [check-version, build]
if: needs.check-version.outputs.do_release == 'true'
runs-on: windows-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
path: AgentBridge
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- uses: actions/download-artifact@v4
with:
name: agentbridge-win-x64
path: win-payload
- name: Extract win-x64 payload
shell: pwsh
run: |
New-Item -ItemType Directory -Path payload | Out-Null
tar -xzf win-payload/agentbridge-win-x64.tar.gz -C payload
- name: Build Windows Store MSI
shell: pwsh
env:
VERSION: ${{ needs.check-version.outputs.version }}
# Store policy 10.2.9: the MSI and every PE file it ships must be signed by a
# certificate chaining to a Microsoft Trusted Root CA. Either a base64 PFX or a
# thumbprint of a certificate present in LocalMachine\My (token/HSM middleware).
SIGN_PFX_BASE64: ${{ secrets.SIGN_PFX_BASE64 }}
SIGN_PFX_PASSWORD: ${{ secrets.SIGN_PFX_PASSWORD }}
SIGN_THUMBPRINT: ${{ secrets.SIGN_THUMBPRINT }}
run: |
$signArgs = @()
$pfx = $null
if ($env:SIGN_PFX_BASE64) {
$pfx = Join-Path $env:RUNNER_TEMP 'store-sign.pfx'
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:SIGN_PFX_BASE64))
$signArgs += @('-SignPfx', $pfx)
if ($env:SIGN_PFX_PASSWORD) { $signArgs += @('-SignPfxPassword', $env:SIGN_PFX_PASSWORD) }
}
elseif ($env:SIGN_THUMBPRINT) {
$signArgs += @('-SignThumbprint', $env:SIGN_THUMBPRINT)
}
else {
Write-Warning 'No signing certificate (SIGN_* secrets) - the MSI will be UNSIGNED and store certification will fail (policy 10.2.9).'
}
try {
& "$env:GITHUB_WORKSPACE/AgentBridge/tools/store/New-StoreInstaller.ps1" `
-PayloadDir "$env:GITHUB_WORKSPACE/payload" `
-Version $env:VERSION `
-OutDir "$env:GITHUB_WORKSPACE/store-msi" `
@signArgs
}
finally {
# Never leave the private key on the runner disk.
if ($pfx -and (Test-Path $pfx)) { Remove-Item $pfx -Force }
}
- name: Upload MSI
uses: actions/upload-artifact@v4
with:
name: store-msi
path: store-msi
# MSIX package for the Microsoft Store — the one Store channel where Microsoft signs for us
# ("The Microsoft Store will automatically re-sign your MSIX/AppX packages with a Microsoft
# certificate"), so no code-signing certificate is needed. Same shape as store-msi: separate job,
# continue-on-error, so a packaging problem can never block or fail the GitHub release.
#
# IdentityName/Publisher MUST match Partner Center (View app identity details) or the upload is
# rejected: set the STORE_IDENTITY_NAME and STORE_PUBLISHER secrets. Without them the package is
# still built (placeholder identity) and uploaded as a CI artifact, but it is not submittable.
# The package is intentionally NOT attached to the GitHub release: an unsigned MSIX cannot be
# sideloaded by users, so publishing it would only create support noise.
# See docs-dev/STORE-PUBLISHING.md section 8.
store-msix:
needs: [check-version, build]
if: needs.check-version.outputs.do_release == 'true'
runs-on: windows-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
path: AgentBridge
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- uses: actions/download-artifact@v4
with:
name: agentbridge-win-x64
path: win-payload
- name: Extract win-x64 payload
shell: pwsh
run: |
New-Item -ItemType Directory -Path payload | Out-Null
tar -xzf win-payload/agentbridge-win-x64.tar.gz -C payload
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Compile PSF (x64 Release) instead of taking it from NuGet
shell: pwsh
run: |
# Why from source: Microsoft ties PSF telemetry collection to the binaries taken from the
# NuGet package -- those are built with Microsoft's real telemetry provider GUID injected
# into include/Telemetry.h (see psf-public-nuget-build.yml in the PSF repo). Building from
# the repo leaves that provider id as the zeroed placeholder, so the packaged app has
# nowhere to send telemetry. There is no runtime switch for it: PsfRuntime reads only
# 'fixups' from config.json.
$ErrorActionPreference = 'Continue'
$psfTag = '1.0.240212.1'
git clone --depth 1 --branch $psfTag `
https://github.com/microsoft/MSIX-PackageSupportFramework.git psf-src
if ($LASTEXITCODE -ne 0) { Write-Warning 'PSF clone failed - falling back to NuGet'; exit 0 }
Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/v6.9.1/nuget.exe' `
-OutFile nuget.exe -UseBasicParsing
& .\nuget.exe restore "psf-src\CentennialFixups.sln"
if ($LASTEXITCODE -ne 0) { Write-Warning 'PSF restore failed - falling back to NuGet'; exit 0 }
# The projects pin the VS2019 toolset (v142); windows-latest ships v143.
msbuild "psf-src\CentennialFixups.sln" -p:Configuration=Release -p:Platform=x64 `
-p:PlatformToolset=v143 -m /v:minimal
if ($LASTEXITCODE -ne 0) {
Write-Warning "PSF build failed (exit $LASTEXITCODE) - falling back to NuGet"; exit 0
}
New-Item -ItemType Directory -Force -Path psf-bin | Out-Null
foreach ($f in 'PsfLauncher64.exe','PsfRuntime64.dll','FileRedirectionFixup64.dll') {
$src = Get-ChildItem -Path psf-src -Recurse -Filter $f -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\x64\\Release\\' } |
Select-Object -First 1
if (-not $src) {
Write-Warning "PSF build did not produce $f - falling back to NuGet"
Remove-Item psf-bin -Recurse -Force -ErrorAction SilentlyContinue
exit 0
}
Copy-Item $src.FullName psf-bin\ -Force
}
Write-Host "PSF built from source into psf-bin (no Microsoft telemetry provider id)"
- name: Build MSIX
shell: pwsh
env:
VERSION: ${{ needs.check-version.outputs.version }}
STORE_IDENTITY_NAME: ${{ secrets.STORE_IDENTITY_NAME }}
STORE_PUBLISHER: ${{ secrets.STORE_PUBLISHER }}
run: |
if (-not $env:STORE_IDENTITY_NAME -or -not $env:STORE_PUBLISHER) {
Write-Warning 'STORE_IDENTITY_NAME / STORE_PUBLISHER are not set: the package carries placeholder identity values and Partner Center would reject the upload.'
}
$msixArgs = @{}
if ($env:STORE_IDENTITY_NAME) { $msixArgs.IdentityName = $env:STORE_IDENTITY_NAME }
if ($env:STORE_PUBLISHER) { $msixArgs.Publisher = $env:STORE_PUBLISHER }
if (Test-Path "$env:GITHUB_WORKSPACE/psf-bin/PsfLauncher64.exe") {
$msixArgs.PsfBinDir = "$env:GITHUB_WORKSPACE/psf-bin"
Write-Host 'Packaging with the from-source PSF binaries.'
} else {
Write-Warning 'From-source PSF unavailable: falling back to the NuGet package, whose binaries carry Microsoft''s telemetry provider id.'
}
& "$env:GITHUB_WORKSPACE/AgentBridge/tools/store/New-StoreMsix.ps1" `
-PayloadDir "$env:GITHUB_WORKSPACE/payload" `
-Version $env:VERSION `
-OutDir "$env:GITHUB_WORKSPACE/store-msix" `
@msixArgs
- name: Upload MSIX
uses: actions/upload-artifact@v4
with:
name: store-msix
path: store-msix/*.msix
release:
needs: [check-version, build, store-msi]
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 + the Windows Store MSI when store-msi produced one (the job is
# continue-on-error, so the archives release must not depend on it — an
# absent MSI artifact just uploads the archives). generate_release_notes:
# auto-notes from merged PRs/commits — every release ships with a changelog
# (read by users and AI engines alike).
#
# `body` is PRE-PENDED to the auto-generated notes (softprops/action-gh-release:
# "If body is specified, the body will be pre-pended to the automatically generated
# notes"), so this is the per-release note. It MUST be updated in the gate-off
# commit — see docs-dev/RELEASE-CHECKLIST.md: a stale note ships the previous
# release's news to every user and to the AI engines that read release pages.
- 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
artifacts/**/*.msi
body: |
### Settings, rebuilt around separate panels
The single tabbed "Main settings" window is gone. Settings now opens as focused
panels — **LLM & Provider**, **Email (SMTP + IMAP)** and **General** — each with
its own **Save** that closes only when its values validate, so a bad value in one
area never blocks the others.
### Provider management is clearer
The **active-provider dropdown** is now the single way to change which provider is
active: pick one, press Save, and the choice persists across restarts. The active
provider is marked **(attivo)** in the list, the dropdown and the list always agree,
and the old "Set default" button is gone. **Edit** opens the provider selected in
the list, and the API-key field shows the stored key masked.
### SIP telephony is now a real settings panel
The SIP page used to be read-only. It is now a full editable panel: enable/disable,
listen port, registrar, credentials, answer mode and PIN, plus call / hang-up /
reload actions.
### Voice moved to the Session menu
Voice starts a one-shot dictation, so it is no longer a "setting": it now lives
under **Session**, alongside the model switch and session features. The TTS engine
panel gained a **reset to default**.
### Documentation on the wiki
The user guides now live on the GitHub wiki, kept in sync automatically from the
repository. **Help → Documentation** (and `/docs`) opens
https://github.com/Graphene-Lab/AgentBridge/wiki.
generate_release_notes: true
# Microsoft Store submission: once the release exists with the MSI attached, point the
# Partner Center draft package at the VERSIONED MSI URL (the AIOffice VPS streaming proxy
# answers 200 without redirects for /agentbridge/msi/<version>, which is pinned to release
# tag v<version> and never changes — Partner Center rejects redirecting URLs and requires
# a URL whose binary stays frozen, policy 10.2.9, see tools/store/vps/) and submit for
# certification. The real gate is inside the run script (the secrets context is not
# allowed in job/step if): the STORE_* secrets exist
# only for accounts that could register the Entra app the Store API requires — individual
# developer accounts have none, so the step exits early and the Store update is manual
# (see tools/store/README.md "Manual fallback"); continue-on-error keeps the run green
# in any case: the GitHub release is already out and a failed Store submission must
# never look like a failed release.
store-submit:
needs: [check-version, store-msi, release]
if: needs.store-msi.result == 'success'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
path: AgentBridge
- name: Submit to Microsoft Store
shell: pwsh
env:
VERSION: ${{ needs.check-version.outputs.version }}
STORE_TENANT_ID: ${{ secrets.STORE_TENANT_ID }}
STORE_CLIENT_ID: ${{ secrets.STORE_CLIENT_ID }}
STORE_CLIENT_SECRET: ${{ secrets.STORE_CLIENT_SECRET }}
STORE_PRODUCT_ID: ${{ secrets.STORE_PRODUCT_ID }}
STORE_SELLER_ID: ${{ secrets.STORE_SELLER_ID }}
run: |
# The secrets context is not usable in if: conditions — gate on the env var
# instead. Without the Entra app credentials (individual accounts) the Store
# update stays manual (see tools/store/README.md "Manual fallback").
if ([string]::IsNullOrEmpty($env:STORE_TENANT_ID)) {
Write-Host 'STORE_TENANT_ID not configured - skipping Store submission (manual fallback).'
exit 0
}
& "$env:GITHUB_WORKSPACE/AgentBridge/tools/store/Submit-Store.ps1" `
-PackageUrl "https://aitechnology.it/agentbridge/msi/$env:VERSION" `
-Version $env:VERSION