Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

1858 lines
86 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build & Release — Electrobun desktop app (signed/notarized on macOS, CEF on Windows/Linux)
# Produces installer artifacts plus platform-prefixed .tar.zst/*-update.json
# update channel files.
#
# Path notes for eliza-native port:
# - The Electrobun shell lives at `packages/app-core/platforms/electrobun/`
# in eliza (not `packages/app/electrobun/` — that directory only carries a
# .gitignore stub). All build artifacts, scripts, and signing helpers live
# under that platforms/electrobun/ path.
# - Helper scripts that did not exist in upstream eliza (ensure-electrobun-core.mjs,
# ensure-legacy-electrobun-compat.mjs, patch-release-check-pack-fallback.mjs)
# have been removed; the corresponding caching/preflight steps are dropped
# and surfaced as TODOs so the user can port them if needed.
#
# Required GitHub Secrets (macOS signing + notarization):
# CSC_LINK base64-encoded Developer ID Application .p12
# CSC_KEY_PASSWORD password for the .p12 certificate
# APPLE_ID Apple ID email for notarization
# APPLE_APP_SPECIFIC_PASSWORD app-specific password from appleid.apple.com
# APPLE_TEAM_ID 10-char Apple Developer Team ID
#
# Configure on elizaOS/eliza repo settings:
# WINDOWS_SIGN_CERT_BASE64, WINDOWS_SIGN_CERT_PASSWORD, WINDOWS_SIGN_TIMESTAMP_URL
# ANTHROPIC_API_KEY (used by Windows packaged smoke test as the boot AI provider)
# ELIZAOS_CLOUD_API_KEY / ELIZACLOUD_API_KEY / ELIZAOS_CLOUD_BASE_URL (optional cloud regression)
#
# Optional release-host upload secrets:
# RELEASE_UPLOAD_KEY SSH private key for releases server
# RELEASE_HOST_FINGERPRINT pinned SSH host key entry for releases host
name: Build & Release (Electrobun)
on:
push:
tags:
- "v*"
workflow_call:
inputs:
tag:
description: "Release tag override (e.g. v2.0.0-beta.0)"
required: false
type: string
draft:
description: "Create the GitHub release as a draft"
required: false
type: boolean
default: false
publish_release:
description: "Create the GitHub release and upload updater files"
required: false
type: boolean
default: false
platform:
description: "Desktop platform matrix to build (all, windows, macos, linux)"
required: false
type: string
default: "all"
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v2.0.0-beta.0)"
required: false
type: string
draft:
description: "Create as draft release"
required: false
type: boolean
default: true
platform:
description: "Desktop platform matrix to build"
required: false
type: choice
default: "all"
options:
- all
- windows
- macos
- linux
concurrency:
group: release-electrobun-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
env:
CI: "true"
BUN_VERSION: "canary" # Rust-rewrite canary runtime (repo-wide migration 2026-05-30). Electrobun bundle creation is Bun-version-sensitive — watch packaging on canary bumps.
jobs:
prepare:
name: Prepare Release
if: >-
${{
github.event_name != 'push' ||
(
!contains(github.ref_name, '-')
)
}}
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
env: ${{ steps.version.outputs.env }}
source_sha: ${{ steps.version.outputs.source_sha }}
desktop_matrix: ${{ steps.desktop-matrix.outputs.desktop_matrix }}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
- name: Determine version + env
id: version
run: |
if [[ -n "${{ inputs.tag }}" ]]; then
TAG="${{ inputs.tag }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
TAG="${{ github.ref_name }}"
else
echo "Manual branch dispatches must provide inputs.tag; refusing to derive a release tag from package.json." >&2
exit 1
fi
VERSION="${TAG#v}"
# canary for pre-release tags, stable for everything else
if [[ "$VERSION" == *"-"* ]]; then
BUILD_ENV="canary"
else
BUILD_ENV="stable"
fi
{
echo "tag=$TAG"
echo "version=$VERSION"
echo "env=$BUILD_ENV"
echo "source_sha=$GITHUB_SHA"
} >> "$GITHUB_OUTPUT"
echo "Release: $VERSION (tag: $TAG, env: $BUILD_ENV, sha: $GITHUB_SHA)"
- name: Select desktop platform matrix
id: desktop-matrix
env:
RELEASE_PLATFORM: ${{ inputs.platform || 'all' }}
run: |
case "$RELEASE_PLATFORM" in
""|all)
printf '%s\n' '{"platform":[{"name":"macOS (Apple Silicon)","os":"macos","runner":"macos-14","artifact-name":"macos-arm64"},{"name":"macOS (Intel)","os":"macos","runner":"macos-15-intel","artifact-name":"macos-x64"},{"name":"Windows","os":"windows","runner":"${{ vars.RUNNER_WINDOWS || 'windows-2025' }}","artifact-name":"windows-x64"},{"name":"Linux","os":"linux","runner":"ubuntu-latest","artifact-name":"linux-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
;;
windows)
printf '%s\n' '{"platform":[{"name":"Windows","os":"windows","runner":"${{ vars.RUNNER_WINDOWS || 'windows-2025' }}","artifact-name":"windows-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
;;
macos)
printf '%s\n' '{"platform":[{"name":"macOS (Apple Silicon)","os":"macos","runner":"macos-14","artifact-name":"macos-arm64"},{"name":"macOS (Intel)","os":"macos","runner":"macos-15-intel","artifact-name":"macos-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
;;
linux)
printf '%s\n' '{"platform":[{"name":"Linux","os":"linux","runner":"ubuntu-latest","artifact-name":"linux-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
;;
*)
echo "::error::Unsupported desktop release platform: $RELEASE_PLATFORM"
exit 1
;;
esac
echo "desktop_matrix=$(tr -d '\n' < "$RUNNER_TEMP/desktop-matrix.json")" >> "$GITHUB_OUTPUT"
cat "$RUNNER_TEMP/desktop-matrix.json"
validate-release:
name: Validate Release Inputs
if: ${{ inputs.platform != 'windows' }}
needs: prepare
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
env:
NODE_NO_WARNINGS: "1"
defaults:
run:
shell: bash -euo pipefail {0}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: "24"
- name: Setup Bun workspace
uses: ./.github/actions/setup-bun-workspace
with:
bun-version: ${{ env.BUN_VERSION }}
install-command: bun install --ignore-scripts
install-native-deps: "true"
run-postinstall: "true"
init-submodules: "false"
# TODO: verify this script exists in eliza or port equivalent.
# In eliza this was `bun run test:regression-matrix:release`.
- name: Regression matrix contract
run: bun run test:regression-matrix:release
- name: Align version with release tag
run: |
node packages/app-core/scripts/align-electrobun-version.mjs
node <<'NODE'
const fs = require("node:fs");
const version = process.env.RELEASE_VERSION;
const packagePath = "packages/app-core/platforms/electrobun/package.json";
const configPath = "packages/app-core/platforms/electrobun/electrobun.config.ts";
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
pkg.version = version;
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
let config = fs.readFileSync(configPath, "utf8");
config = config.replace(/version:\s*"[^"]+"/, `version: "${version}"`);
fs.writeFileSync(configPath, config);
NODE
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
- name: Ensure avatar assets
run: node packages/app-core/scripts/ensure-avatars.mjs
- name: Build core dist (server bundle)
run: |
node --input-type=module <<'NODE'
import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const hasRootTsdownEntry =
fs.existsSync(path.join(root, "tsdown.config.ts")) ||
fs.existsSync(path.join(root, "tsdown.config.mts")) ||
fs.existsSync(path.join(root, "tsdown.config.js")) ||
fs.existsSync(path.join(root, "src", "index.ts"));
if (hasRootTsdownEntry) {
const { spawnSync } = await import("node:child_process");
const result = spawnSync("bunx", ["tsdown", "--fail-on-warn", "false"], {
cwd: root,
stdio: "inherit",
shell: process.platform === "win32",
});
process.exit(result.status ?? 1);
}
const entryTarget = "../packages/app-core/src/entry.ts";
const entrySource = [
"// auto-generated by release-electrobun.yml",
"// Standalone elizaOS checkouts do not have a root tsdown entry.",
`export * from ${JSON.stringify(entryTarget)};`,
"",
].join("\n");
fs.mkdirSync(path.join(root, "dist"), { recursive: true });
fs.writeFileSync(path.join(root, "dist", "entry.js"), entrySource);
fs.writeFileSync(path.join(root, "dist", "index.js"), entrySource);
fs.writeFileSync(path.join(root, "dist", "package.json"), '{"type":"module"}\n');
NODE
node --import tsx packages/scripts/write-build-info.ts
- name: Run heavy E2E regression suite
# TODO: verify this target exists in eliza root package.json.
run: bun run test:e2e:heavy
- name: Probe optional cloud live regression key
id: probe-cloud-live-key
env:
ELIZAOS_CLOUD_API_KEY: ${{ secrets.ELIZAOS_CLOUD_API_KEY != '' && secrets.ELIZAOS_CLOUD_API_KEY || secrets.ELIZACLOUD_API_KEY }}
run: |
if [ -n "${ELIZAOS_CLOUD_API_KEY:-}" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::No Eliza Cloud API key configured for release validation - skipping optional cloud live regression."
echo "available=false" >> "$GITHUB_OUTPUT"
fi
- name: Run optional cloud live regression suite
if: steps.probe-cloud-live-key.outputs.available == 'true'
shell: bash
run: |
set -o pipefail
log_file="$(mktemp)"
if bun run test:live:cloud 2>&1 | tee "$log_file"; then
exit 0
fi
echo "::warning::Optional cloud live regression failed; release validation continues with deterministic build and packaging checks."
exit 0
env:
ELIZA_LIVE_TEST: "1"
ELIZAOS_CLOUD_API_KEY: ${{ secrets.ELIZAOS_CLOUD_API_KEY != '' && secrets.ELIZAOS_CLOUD_API_KEY || secrets.ELIZACLOUD_API_KEY }}
ELIZAOS_CLOUD_BASE_URL: ${{ secrets.ELIZAOS_CLOUD_BASE_URL }}
- name: Restore build metadata after test rebuilds
run: node --import tsx packages/scripts/write-build-info.ts
- name: Generate release validation manifests
run: |
node packages/app-core/scripts/write-homepage-release-data.mjs
node packages/app-core/scripts/generate-static-asset-manifest.mjs
- name: Release readiness checks
env:
ELIZA_RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
ELIZA_CDN_VALIDATION_REF: ${{ github.sha }}
ELIZA_VALIDATE_CDN: "1"
run: bun run release:check
build-browser-companions:
name: Build Agent Browser Bridge companions
if: ${{ inputs.platform == '' || inputs.platform == 'all' }}
needs: [prepare, validate-release]
runs-on: macos-15
timeout-minutes: 60
outputs:
packaged: ${{ steps.package-browser-bridge.outputs.packaged }}
defaults:
run:
shell: bash -euo pipefail {0}
env:
ELIZA_RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: "24"
- name: Setup Bun workspace
uses: ./.github/actions/setup-bun-workspace
with:
bun-version: ${{ env.BUN_VERSION }}
install-command: bun install --ignore-scripts
install-native-deps: "false"
run-postinstall: "true"
init-submodules: "false"
- name: Package Agent Browser Bridge release bundles
id: package-browser-bridge
run: |
if bun run browser-bridge:package:release; then
echo "packaged=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Agent Browser Bridge packaging failed; desktop release will continue without browser companion bundles."
echo "packaged=false" >> "$GITHUB_OUTPUT"
fi
- name: Upload Agent Browser Bridge release artifacts
if: steps.package-browser-bridge.outputs.packaged == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: browser-bridge-store-bundles
path: |
packages/browser-extension/dist/artifacts/browser-bridge-chrome-v*.zip
packages/browser-extension/dist/artifacts/browser-bridge-safari-v*.zip
packages/browser-extension/dist/artifacts/browser-bridge-safari-project-v*.zip
packages/browser-extension/dist/artifacts/browser-bridge-release-manifest-v*.json
if-no-files-found: error
retention-days: 30
build:
name: Build ${{ matrix.platform.name }}
if: ${{ always() && needs.prepare.result == 'success' && (needs.validate-release.result == 'success' || needs.validate-release.result == 'skipped') }}
needs: [prepare, validate-release]
runs-on: ${{ matrix.platform.runner }}
timeout-minutes: 150
defaults:
run:
shell: bash -euo pipefail {0}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare.outputs.desktop_matrix) }}
steps:
- name: Enable Git long paths (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
function Set-GitLongPaths([string]$Scope) {
& git config $Scope core.longpaths true
if ($LASTEXITCODE -ne 0) {
throw "git config $Scope core.longpaths true failed with exit code $LASTEXITCODE"
}
}
try {
Set-GitLongPaths "--system"
Write-Host "Enabled Git long paths in system config."
} catch {
Write-Warning "System git config failed; falling back to --global. $($_.Exception.Message)"
Set-GitLongPaths "--global"
Write-Host "Enabled Git long paths in global config."
}
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
submodules: false
- name: Validate public desktop signing inputs
env:
BUILD_ENV: ${{ needs.prepare.outputs.env }}
PUBLISH_RELEASE: ${{ inputs.publish_release || github.event_name == 'push' }}
PLATFORM_OS: ${{ matrix.platform.os }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
run: |
if [[ "$PUBLISH_RELEASE" != "true" && "$BUILD_ENV" != "stable" ]]; then
echo "Non-published canary build; signing inputs are advisory."
exit 0
fi
missing=()
case "$PLATFORM_OS" in
macos)
[[ -z "$CSC_LINK" ]] && missing+=("CSC_LINK")
[[ -z "$CSC_KEY_PASSWORD" ]] && missing+=("CSC_KEY_PASSWORD")
[[ -z "$APPLE_ID" ]] && missing+=("APPLE_ID")
[[ -z "$APPLE_APP_SPECIFIC_PASSWORD" ]] && missing+=("APPLE_APP_SPECIFIC_PASSWORD")
[[ -z "$APPLE_TEAM_ID" ]] && missing+=("APPLE_TEAM_ID")
;;
windows)
[[ -z "$WINDOWS_SIGN_CERT_BASE64" ]] && missing+=("WINDOWS_SIGN_CERT_BASE64")
[[ -z "$WINDOWS_SIGN_CERT_PASSWORD" ]] && missing+=("WINDOWS_SIGN_CERT_PASSWORD")
[[ -z "$WINDOWS_SIGN_TIMESTAMP_URL" ]] && missing+=("WINDOWS_SIGN_TIMESTAMP_URL")
;;
esac
if [[ ${#missing[@]} -gt 0 ]]; then
echo "::error::Missing public desktop signing inputs for $PLATFORM_OS: ${missing[*]}"
exit 1
fi
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: "24"
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install Linux packaging tools
if: matrix.platform.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
curl \
desktop-file-utils \
file \
rpm
- name: Install root dependencies
# --ignore-scripts skips native dependency install scripts (e.g. @tensorflow/tfjs-node
# which has no pre-built binary for Node 22 on Windows and fails to compile).
# Workspace prepare scripts are NOT suppressed by --ignore-scripts in bun.
# macOS Intel: keep the x64 invocation explicit in case the runner image
# ships with dual-arch shims.
run: |
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
install_cmd=(arch -x86_64 bun install --ignore-scripts)
else
install_cmd=(bun install --ignore-scripts)
fi
for attempt in 1 2 3; do
if "${install_cmd[@]}"; then
exit 0
fi
if [ "$attempt" -eq 3 ]; then
echo "bun install failed after ${attempt} attempts" >&2
exit 1
fi
echo "bun install failed on attempt ${attempt}; retrying in 15 seconds" >&2
sleep 15
done
- name: Run repository postinstall patches
run: bun run postinstall
- name: Align version with release tag
run: |
node packages/app-core/scripts/align-electrobun-version.mjs
node <<'NODE'
const fs = require("node:fs");
const version = process.env.RELEASE_VERSION;
const packagePath = "packages/app-core/platforms/electrobun/package.json";
const configPath = "packages/app-core/platforms/electrobun/electrobun.config.ts";
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
pkg.version = version;
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
let config = fs.readFileSync(configPath, "utf8");
config = config.replace(/version:\s*"[^"]+"/, `version: "${version}"`);
fs.writeFileSync(configPath, config);
NODE
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
- name: Generate i18n keyword data
run: |
node packages/shared/scripts/generate-keywords.mjs
test -f packages/shared/src/i18n/generated/validation-keyword-data.js
- name: Stage desktop bundle inputs
run: |
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
ELIZA_DESKTOP_COMMAND_PREFIX="arch -x86_64" node packages/app-core/scripts/desktop-build.mjs stage --variant=base
else
node packages/app-core/scripts/desktop-build.mjs stage --variant=base
fi
mkdir -p dist/node_modules/@elizaos/shared/src/i18n/generated
cp packages/shared/src/i18n/generated/validation-keyword-data.ts dist/node_modules/@elizaos/shared/src/i18n/generated/
cp packages/shared/src/i18n/generated/validation-keyword-data.js dist/node_modules/@elizaos/shared/src/i18n/generated/
test -f dist/node_modules/@elizaos/shared/src/i18n/generated/validation-keyword-data.js
- name: Validate Electrobun runtime copy contract
env:
ELIZA_ELECTROBUN_REPO_ROOT: ${{ github.workspace }}
run: |
test -f dist/entry.js
test -f package.json
test -f dist/node_modules/@elizaos/agent/package.json
test -f dist/node_modules/@elizaos/shared/src/i18n/generated/validation-keyword-data.js
node --import tsx <<'NODE'
const path = await import("node:path");
const configModule = await import("./packages/app-core/platforms/electrobun/electrobun.config.ts");
const config = configModule.default?.default ?? configModule.default;
const electrobunDir = path.resolve("packages/app-core/platforms/electrobun");
const copyMap = config.build?.copy ?? {};
const runtimeSource = Object.entries(copyMap).find(
([, destination]) => destination === "eliza-dist",
)?.[0];
if (!runtimeSource) {
throw new Error("Electrobun copy map does not include eliza-dist runtime destination");
}
const resolvedRuntimeSource = path.resolve(electrobunDir, runtimeSource);
const expectedRuntimeSource = path.resolve("dist");
if (resolvedRuntimeSource !== expectedRuntimeSource) {
throw new Error(
`Electrobun runtime source resolves to ${resolvedRuntimeSource}, expected ${expectedRuntimeSource}`,
);
}
NODE
# Import the Developer ID certificate into a temporary keychain.
# Requires CSC_LINK (base64-encoded .p12) + CSC_KEY_PASSWORD.
# The signing identity string is extracted and passed to Electrobun as
# ELECTROBUN_DEVELOPER_ID in the build step below.
- name: Set up macOS signing keychain
if: matrix.platform.os == 'macos'
id: macos-keychain
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
run: |
if [ -z "$CSC_LINK" ]; then
echo "::warning::CSC_LINK not set — building unsigned (no codesign/notarize)"
echo "skip_codesign=1" >> "$GITHUB_OUTPUT"
exit 0
fi
KEYCHAIN_PATH="$RUNNER_TEMP/eliza-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
# Create a short-lived keychain for this build
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Decode and import the .p12 certificate
CERT_PATH="$RUNNER_TEMP/cert.p12"
echo "$CSC_LINK" | base64 --decode > "$CERT_PATH"
security import "$CERT_PATH" \
-k "$KEYCHAIN_PATH" \
-P "$CSC_KEY_PASSWORD" \
-T /usr/bin/codesign \
-T /usr/bin/security
rm -f "$CERT_PATH"
# Add to the keychain search list (preserving login keychain)
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
# Allow codesign to access the key without a password prompt
security set-key-partition-list -S apple-tool:,apple:,codesign: -s \
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Extract identity for Electrobun
IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" \
| grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)"/\1/')
if [ -z "$IDENTITY" ]; then
echo "::error::No 'Developer ID Application' identity found in certificate"
exit 1
fi
echo "Signing identity: $IDENTITY"
echo "developer_id=$IDENTITY" >> "$GITHUB_OUTPUT"
echo "skip_codesign=" >> "$GITHUB_OUTPUT"
- name: Sign native macOS effects dylib
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign != '1'
env:
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
run: |
DYLIB="packages/app-core/platforms/electrobun/src/libMacWindowEffects.dylib"
if [ ! -f "$DYLIB" ]; then
echo "::error::Expected $DYLIB to exist after build:native-effects"
exit 1
fi
codesign --force --timestamp --sign "$ELECTROBUN_DEVELOPER_ID" "$DYLIB"
codesign --verify --strict --verbose=2 "$DYLIB"
- name: Install quiet macOS packaging wrappers
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign != '1'
run: |
wrapper_dir="$RUNNER_TEMP/eliza-notary-bin"
mkdir -p "$wrapper_dir"
cp packages/app-core/platforms/electrobun/scripts/hdiutil-wrapper.sh "$wrapper_dir/hdiutil"
cp packages/app-core/platforms/electrobun/scripts/xcrun-wrapper.sh "$wrapper_dir/xcrun"
cp packages/app-core/platforms/electrobun/scripts/zip-wrapper.sh "$wrapper_dir/zip"
chmod +x "$wrapper_dir/hdiutil"
chmod +x "$wrapper_dir/xcrun"
chmod +x "$wrapper_dir/zip"
echo "$wrapper_dir" >> "$GITHUB_PATH"
- name: Resolve electrobun package dir
id: resolve-electrobun
shell: bash
run: |
package_dir="$(node <<'NODE'
const { createRequire } = require("node:module");
const fs = require("node:fs");
const path = require("node:path");
const workspacePackageJson = path.resolve("packages/app-core/platforms/electrobun/package.json");
const req = createRequire(workspacePackageJson);
const entryPath = req.resolve("electrobun");
let packageDir = path.dirname(entryPath);
while (!fs.existsSync(path.join(packageDir, "package.json"))) {
const parentDir = path.dirname(packageDir);
if (parentDir === packageDir) {
throw new Error(
`Could not find electrobun package.json starting from ${entryPath}`,
);
}
packageDir = parentDir;
}
const manifestPath = path.join(packageDir, "package.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
if (manifest.name !== "electrobun") {
throw new Error(
`Resolved unexpected package at ${manifestPath}: ${manifest.name}`,
);
}
process.stdout.write(packageDir);
NODE
)"
if [ -z "$package_dir" ]; then
echo "::error::Failed to resolve electrobun package directory"
exit 1
fi
case "${{ matrix.platform.artifact-name }}" in
macos-arm64|macos-x64|linux-x64|linux-arm64)
electrobun_core_target="${{ matrix.platform.artifact-name }}"
;;
windows-x64)
electrobun_core_target="win-x64"
;;
*)
echo "::error::Unsupported Electrobun release artifact target: ${{ matrix.platform.artifact-name }}"
exit 1
;;
esac
echo "Resolved electrobun package dir: $package_dir"
echo "package-dir=$package_dir" >> "$GITHUB_OUTPUT"
echo "cache-dir=$package_dir/.cache" >> "$GITHUB_OUTPUT"
echo "core-target=$electrobun_core_target" >> "$GITHUB_OUTPUT"
echo "core-cache-dir=$package_dir/dist-$electrobun_core_target" >> "$GITHUB_OUTPUT"
- name: Cache Electrobun CLI and core binaries
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
${{ steps.resolve-electrobun.outputs.cache-dir }}
${{ steps.resolve-electrobun.outputs.core-cache-dir }}
key: electrobun-core-${{ matrix.platform.artifact-name }}-${{ hashFiles('packages/app-core/platforms/electrobun/package.json') }}
restore-keys: electrobun-core-${{ matrix.platform.artifact-name }}-
# TODO: verify this script exists in eliza. `ensure-electrobun-core.mjs`
# was eliza-only and is not present in upstream eliza/packages/app-core/scripts/.
# Without it the cache step above is decorative — Electrobun will fall
# back to its own download flow on first build. Port the script if you
# want pre-warmed core binaries on the first packaging run.
- name: Prepare Electrobun core binaries
run: |
if [ -f packages/app-core/scripts/ensure-electrobun-core.mjs ]; then
node packages/app-core/scripts/ensure-electrobun-core.mjs \
--package-dir "${{ steps.resolve-electrobun.outputs.package-dir }}" \
--target "${{ steps.resolve-electrobun.outputs.core-target }}"
else
echo "::warning::packages/app-core/scripts/ensure-electrobun-core.mjs not present — Electrobun will download core binaries on demand."
fi
- name: Probe Electrobun bun entry build
working-directory: packages/app-core/platforms/electrobun
run: |
out_dir="$RUNNER_TEMP/electrobun-entry-probe"
node "$GITHUB_WORKSPACE/packages/scripts/rm-path-recursive.mjs" "$out_dir"
bun build src/index.ts --target=bun --packages=external --outdir "$out_dir"
- name: Build patched Electrobun CLI
shell: bash
run: |
node packages/app-core/scripts/build-patched-electrobun-cli.mjs "${{ steps.resolve-electrobun.outputs.package-dir }}" "${{ matrix.platform.artifact-name }}"
- name: Build Electrobun app
id: build-electrobun-app
run: |
set +e
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
ELIZA_DESKTOP_COMMAND_PREFIX="arch -x86_64" node packages/app-core/scripts/desktop-build.mjs package --env=${{ needs.prepare.outputs.env }}
else
node packages/app-core/scripts/desktop-build.mjs package --env=${{ needs.prepare.outputs.env }}
fi
status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "fallback=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ inputs.draft }}" != "true" ] || [ "${{ inputs.publish_release }}" = "true" ]; then
exit "$status"
fi
artifact_root="packages/app-core/platforms/electrobun/artifacts"
build_root="packages/app-core/platforms/electrobun/build"
build_dir="$(find "$build_root" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | tail -1 || true)"
if [ -z "$build_dir" ]; then
echo "::error::Electrobun packaging failed and no built app tree exists"
exit "$status"
fi
VERSION="$(node -p "require('./packages/app-core/platforms/electrobun/package.json').version")"
ENV_NAME="${{ needs.prepare.outputs.env }}"
find -L "$build_dir" -type d -name "Resources" | while read -r res_dir; do
dest="$res_dir/version.json"
printf '{"identifier":"ai.elizaos.Eliza","channel":"%s","name":"eliza","version":"%s"}' \
"$ENV_NAME" "$VERSION" > "$dest"
echo "Wrote fallback $dest"
done
mkdir -p "$artifact_root"
case "${{ matrix.platform.os }}" in
linux)
tar --zstd -cf "$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.tar.zst" -C "$build_root" "$(basename "$build_dir")"
;;
macos)
tar -czf "$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.app.tar.gz" -C "$build_root" "$(basename "$build_dir")"
;;
windows)
powershell -NoProfile -Command "Compress-Archive -Path '$build_dir' -DestinationPath '$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.exe.zip' -Force"
;;
esac
echo "fallback=true" >> "$GITHUB_OUTPUT"
echo "::warning::Electrobun package command exited with $status; uploaded draft-validation fallback archive from $build_dir"
env:
# Electrobun reads these env vars directly. The workflow derives the
# signing identity from CSC_LINK/CSC_KEY_PASSWORD during keychain setup
# and maps the notarization credentials from the standard repo secrets.
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
ELECTROBUN_APPLEID: ${{ secrets.APPLE_ID }}
ELECTROBUN_APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
ELECTROBUN_TEAMID: ${{ secrets.APPLE_TEAM_ID }}
ELECTROBUN_SKIP_CODESIGN: ${{ steps.macos-keychain.outputs.skip_codesign }}
ELECTROBUN_REAL_HDIUTIL: /usr/bin/hdiutil
ELIZA_ELECTROBUN_NOTARIZE: 0
ELECTROBUN_REAL_XCRUN: /usr/bin/xcrun
ELECTROBUN_REAL_ZIP: /usr/bin/zip
ELIZA_ELECTROBUN_REPO_ROOT: ${{ github.workspace }}
# Flat release root for the updater. Env is encoded in the platform prefix
# (canary-macos-arm64-update.json etc.), so the baseUrl has no env subdir.
# Points to GitHub Releases as the CDN; the ota-publish job attaches
# latest-{channel}.json after the release job completes.
ELIZA_RELEASE_URL: ${{ (github.event_name != 'workflow_call' || inputs.publish_release) && !inputs.draft && format('https://github.com/{0}/releases/download/{1}/', github.repository, needs.prepare.outputs.tag) || '' }}
- name: Dump Electrobun build diagnostics
if: failure()
run: |
root="packages/app-core/platforms/electrobun"
echo "=== ${root}/build ==="
find -L "$root/build" -maxdepth 6 -type f -print 2>/dev/null | sort | head -300 || true
echo "=== ${root}/artifacts ==="
find -L "$root/artifacts" -maxdepth 6 -type f -print 2>/dev/null | sort | head -300 || true
echo "=== ${root} wrapper diagnostics ==="
find -L "$root/build" -name wrapper-diagnostics.json -print -exec sed -n '1,220p' {} \; 2>/dev/null || true
- name: Upload Electrobun build diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}-build-diagnostics
path: |
packages/app-core/platforms/electrobun/build/**
packages/app-core/platforms/electrobun/build/**/wrapper-diagnostics.json
packages/app-core/platforms/electrobun/artifacts/**
if-no-files-found: warn
retention-days: 14
# Inject version.json into the packaged Resources directory.
# The bun entry getVersionInfo() reads ../Resources/version.json and
# throws if absent, killing the process before main() runs.
# Electrobun does not generate this file for us, so write it from the
# release metadata after the packaged bundle exists.
- name: Inject version.json into bundle (Windows)
if: matrix.platform.os == 'windows'
shell: pwsh
run: |
$version = (Get-Content packages/app-core/platforms/electrobun/package.json | ConvertFrom-Json).version
$envName = "${{ needs.prepare.outputs.env }}"
$buildRoot = "packages/app-core/platforms/electrobun/build"
Get-ChildItem -Path $buildRoot -Recurse -Directory -Filter "Resources" | ForEach-Object {
$versionJson = @{
identifier = "ai.elizaos.Eliza"
channel = $envName
name = "eliza"
version = $version
} | ConvertTo-Json -Compress
$dest = Join-Path $_.FullName "version.json"
Write-Host "Writing $dest"
Set-Content -Path $dest -Value $versionJson -Encoding utf8
}
- name: Inject version.json into bundle (macOS / Linux)
if: matrix.platform.os != 'windows'
run: |
VERSION="$(node -p "require('./packages/app-core/platforms/electrobun/package.json').version")"
ENV_NAME="${{ needs.prepare.outputs.env }}"
build_root="packages/app-core/platforms/electrobun/build"
if [ -d "$build_root" ]; then
find -L "$build_root" -type d -name "Resources" | while read -r res_dir; do
dest="$res_dir/version.json"
printf '{"identifier":"ai.elizaos.Eliza","channel":"%s","name":"eliza","version":"%s"}' \
"$ENV_NAME" "$VERSION" > "$dest"
echo "Wrote $dest"
done
fi
- name: Package Linux desktop installers
if: matrix.platform.os == 'linux' && steps.build-electrobun-app.outputs.fallback != 'true'
run: |
node packages/app-core/scripts/package-electrobun-linux.mjs \
--version=${{ needs.prepare.outputs.version }} \
--channel=${{ needs.prepare.outputs.env }} \
--arch=x64
- name: List build output
run: |
echo "=== Electrobun artifacts ==="
find -L packages/app-core/platforms/electrobun/artifacts -type f 2>/dev/null | sort || echo "(no artifacts directory)"
- name: Stage standard macOS release app
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
env:
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
ELECTROBUN_APPLEID: ${{ secrets.APPLE_ID }}
ELECTROBUN_APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
ELECTROBUN_TEAMID: ${{ secrets.APPLE_TEAM_ID }}
ELECTROBUN_SKIP_CODESIGN: ${{ steps.macos-keychain.outputs.skip_codesign }}
run: bash packages/app-core/platforms/electrobun/scripts/stage-macos-release-artifacts.sh
- name: Sign Windows executables
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
env:
# Configure on elizaOS/eliza repo settings.
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
shell: pwsh
run: pwsh -File packages/app-core/platforms/electrobun/scripts/sign-windows.ps1 -ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") -BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build")
- name: Install Inno Setup 6.7.1
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$url = "https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe"
$installer = Join-Path $env:RUNNER_TEMP "innosetup-6.7.1.exe"
Write-Host "Downloading Inno Setup 6.7.1..."
Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing
Write-Host "Installing silently..."
Start-Process -FilePath $installer -ArgumentList "/VERYSILENT","/SUPPRESSMSGBOXES","/NORESTART","/SP-" -Wait -NoNewWindow
$iscc = @(
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
"C:\Program Files\Inno Setup 6\ISCC.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $iscc) {
Write-Error "ISCC.exe not found after installing Inno Setup 6.7.1"
exit 1
}
Add-Content -Path $env:GITHUB_ENV -Value "ELIZA_INNO_SETUP_COMPILER=$iscc"
Write-Host "Using ISCC compiler: $iscc"
- name: Extract Windows app bundle for Inno Setup
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
$tarball = Get-ChildItem -Path $artifactsDir -File -Filter "*.tar.zst" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $tarball) {
Write-Error "No .tar.zst archive found in artifacts"
exit 1
}
# Use a short path to avoid Windows MAX_PATH (260 char) limits
# with deeply nested node_modules inside the app bundle
$extractDir = "C:\e"
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
Write-Host "Extracting $($tarball.Name) to $extractDir ..."
tar -xf $tarball.FullName -C $extractDir
Write-Host "Extracted top-level structure:"
Get-ChildItem -Path $extractDir -Recurse -Depth 3 | ForEach-Object { Write-Host " $($_.FullName)" }
# Verify the expected runtime entry point exists without mutating the
# extracted installer source tree. Inno follows recursively, so
# junction/copy aliases here can duplicate the deep runtime tree and
# trip Windows path handling during compilation.
$appRoot = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1
if ($appRoot) {
$resApp = Join-Path $appRoot.FullName "Resources\app"
$elizaDist = Join-Path $resApp "eliza-dist"
$entryCheckEliza = Join-Path $elizaDist "entry.js"
Write-Host "Checking for: $entryCheckEliza"
if (Test-Path $entryCheckEliza) {
Write-Host "eliza-dist/entry.js found"
} else {
Write-Host "WARNING: eliza-dist/entry.js NOT found"
Write-Host "Resources/app contents:"
if (Test-Path $resApp) {
Get-ChildItem -Path $resApp -Recurse -Depth 2 | ForEach-Object { Write-Host " $($_.FullName)" }
} else {
Write-Host " Resources/app directory does not exist"
}
}
$keywordDataCheck = Join-Path $elizaDist "node_modules\@elizaos\shared\src\i18n\generated\validation-keyword-data.js"
Write-Host "Checking for: $keywordDataCheck"
if (Test-Path $keywordDataCheck) {
Write-Host "eliza-dist generated keyword data found"
} else {
Write-Error "eliza-dist generated keyword data NOT found: $keywordDataCheck"
exit 1
}
$agentManifestPath = Join-Path $elizaDist "node_modules\@elizaos\agent\package.json"
Write-Host "Checking for: $agentManifestPath"
if (Test-Path $agentManifestPath) {
Write-Host "eliza-dist @elizaos/agent package manifest found"
} else {
Write-Error "eliza-dist @elizaos/agent package manifest NOT found: $agentManifestPath"
exit 1
}
}
- name: Build Inno Setup installer
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
env:
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
shell: pwsh
run: |
pwsh -File packages/app-core/packaging/inno/build-inno.ps1 `
-BuildDir "C:\e" `
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
-Version "${{ needs.prepare.outputs.version }}" `
-Channel "${{ needs.prepare.outputs.env }}"
- name: Verify Windows public installer looks complete
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$installer = Get-ChildItem -Path "packages/app-core/platforms/electrobun/artifacts" -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $installer) {
Write-Error "No public Windows installer found in packages/app-core/platforms/electrobun/artifacts"
exit 1
}
$minimumBytes = 50MB
if ($installer.Length -lt $minimumBytes) {
Write-Error "Installer is too small to be a standalone Windows package: $($installer.FullName) ($($installer.Length) bytes)"
exit 1
}
Write-Host "Verified standalone Windows installer: $($installer.Name)"
Write-Host "Installer size: $([math]::Round($installer.Length / 1MB, 1)) MB"
- name: Cache Windows embedding model
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: ~/.eliza/models
key: embedding-model-nomic-embed-text-v1.5-Q4_K_S
- name: Seed Windows embedding model cache
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$modelsDir = Join-Path $env:USERPROFILE ".eliza\models"
$modelName = "nomic-embed-text-v1.5.Q4_K_S.gguf"
$modelRepo = "nomic-ai/nomic-embed-text-v1.5-GGUF"
$modelPath = Join-Path $modelsDir $modelName
if (-not (Test-Path $modelPath)) {
New-Item -ItemType Directory -Force -Path $modelsDir | Out-Null
$url = "https://huggingface.co/$modelRepo/resolve/main/$modelName"
Write-Host "Downloading Windows embedding model cache seed: $url"
Invoke-WebRequest -Uri $url -OutFile $modelPath
}
Write-Host "Windows embedding model cache ready: $modelPath"
- name: Smoke test packaged Windows app
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
timeout-minutes: 30
shell: pwsh
env:
ELIZA_DISABLE_LOCAL_EMBEDDINGS: "1"
# The runtime requires at least one AI provider plugin to fully
# initialize — without it, startApiServer() can fail before
# server.listen() and the health endpoint never becomes reachable.
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ELIZA_WINDOWS_SMOKE_REQUIRE_INSTALLER: "1"
# Use a short path under runner.temp to avoid MAX_PATH issues with
# deeply nested node_modules while staying in a user-writable location.
ELIZA_TEST_WINDOWS_INSTALL_DIR: ${{ runner.temp }}\el
ELIZA_TEST_WINDOWS_LAUNCHER_DIR: ${{ runner.temp }}\eliza-windows-ui-launcher
ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE: ${{ runner.temp }}\eliza-windows-ui-launcher.txt
# Inno installer is built into platforms/electrobun/artifacts.
ELIZA_TEST_WINDOWS_ARTIFACTS_DIR: ${{ github.workspace }}\packages\app-core\platforms\electrobun\artifacts
ELIZA_TEST_WINDOWS_BUILD_DIR: ${{ github.workspace }}\packages\app-core\platforms\electrobun\build
run: |
Remove-Item $env:ELIZA_TEST_WINDOWS_INSTALL_DIR -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item $env:ELIZA_TEST_WINDOWS_LAUNCHER_DIR -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE -Force -ErrorAction SilentlyContinue
bun run test:desktop:packaged:windows
if ($LASTEXITCODE -ne 0) {
Write-Error "Packaged Windows smoke test exited with code $LASTEXITCODE."
exit $LASTEXITCODE
}
if (-not (Test-Path $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE)) {
Write-Error "Smoke test did not emit a reusable Windows launcher path."
exit 1
}
$launcherPath = (Get-Content $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE -Raw).Trim()
if ([string]::IsNullOrWhiteSpace($launcherPath) -or -not (Test-Path $launcherPath)) {
Write-Error "Reusable Windows launcher path is invalid: $launcherPath"
exit 1
}
Add-Content -Path $env:GITHUB_ENV -Value "ELIZA_TEST_WINDOWS_LAUNCHER_PATH=$launcherPath"
Write-Host "Reusing packaged Windows launcher for Playwright: $launcherPath"
- name: Run Windows clean installer proof
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
env:
ELIZA_DISABLE_LOCAL_EMBEDDINGS: "1"
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR: ${{ runner.temp }}\el-proof
run: |
Remove-Item $env:ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR -Recurse -Force -ErrorAction SilentlyContinue
pwsh -File packages/app-core/platforms/electrobun/scripts/verify-windows-installer-proof.ps1 `
-ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
-BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build") `
-ProofInstallDir $env:ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR `
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts/windows-installer-proof")
if ($LASTEXITCODE -ne 0) {
Write-Error "Windows clean installer proof exited with code $LASTEXITCODE."
exit $LASTEXITCODE
}
- name: Upload Windows installer proof artifact
if: always() && matrix.platform.os == 'windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-windows-installer-proof
path: packages/app-core/platforms/electrobun/artifacts/windows-installer-proof/**
if-no-files-found: warn
retention-days: 14
- name: Upload Windows installer .exe + Inno log on smoke failure
if: failure() && matrix.platform.os == 'windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-windows-installer-debug
path: |
packages/app-core/platforms/electrobun/artifacts/*.exe
${{ runner.temp }}/eliza-inno-setup.log
if-no-files-found: warn
retention-days: 14
- name: Run Windows packaged renderer bootstrap check
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
run: bun run test:desktop:playwright
- name: Upload Windows Playwright UI diagnostics
if: failure() && matrix.platform.os == 'windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-windows-ui-playwright-diagnostics
# TODO: confirm packaged-app Playwright result location in eliza
# (was apps/app/test-results/** in eliza; might be packages/app/test-results/**).
path: packages/app/test-results/**
if-no-files-found: warn
retention-days: 14
- name: Stage Windows setup executables
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
$setupExecutables = Get-ChildItem -Path "packages/app-core/platforms/electrobun/build" -Recurse -File -Filter "*Setup*.exe" -ErrorAction SilentlyContinue
if (-not $setupExecutables) {
Write-Error "No Windows setup executable found under packages/app-core/platforms/electrobun/build"
exit 1
}
New-Item -ItemType Directory -Force -Path $artifactsDir | Out-Null
$publicInstaller = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
foreach ($setupExecutable in $setupExecutables) {
$destinationPath = Join-Path $artifactsDir $setupExecutable.Name
if (
$publicInstaller -and
$publicInstaller.Name -eq $setupExecutable.Name -and
$publicInstaller.Length -ge 50MB -and
$setupExecutable.Length -lt $publicInstaller.Length
) {
Write-Warning "Skipping build setup stub that would overwrite verified public installer: $($setupExecutable.FullName)"
continue
}
if (Test-Path $destinationPath) {
Write-Host "Skipping setup executable already present in artifacts: $destinationPath"
continue
}
Copy-Item $setupExecutable.FullName -Destination $destinationPath -Force
Write-Host "Staged Windows setup executable: $($setupExecutable.Name)"
}
- name: Sign Windows executables (post-stage)
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
env:
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
shell: pwsh
run: pwsh -File packages/app-core/platforms/electrobun/scripts/sign-windows.ps1 -ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") -BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build")
- name: Verify Windows code signature
if: matrix.platform.os == 'windows' && env.WINDOWS_SIGN_CERT_BASE64 != ''
env:
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
shell: pwsh
run: |
$signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter "signtool.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "x64" } |
Sort-Object { [version]($_.FullName -replace '.*\\(\d+\.\d+\.\d+\.\d+)\\.*', '$1') } -Descending |
Select-Object -First 1 -ExpandProperty FullName
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
$signed = 0
$failed = 0
Get-ChildItem -Path $artifactsDir -Recurse -Filter "*.exe" -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "Verifying: $($_.FullName)"
& $signtool verify /pa /v $_.FullName
if ($LASTEXITCODE -eq 0) {
$signed++
} else {
Write-Host "::error::Signature verification failed: $($_.Name)"
$failed++
}
}
Write-Host "Verification complete: $signed signed, $failed failed"
if ($failed -gt 0) {
exit 1
}
- name: Re-verify Windows public installer before upload
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$installer = Get-ChildItem -Path "packages/app-core/platforms/electrobun/artifacts" -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $installer) {
Write-Error "No public Windows installer found in packages/app-core/platforms/electrobun/artifacts after staging/signing."
exit 1
}
$minimumBytes = 50MB
if ($installer.Length -lt $minimumBytes) {
Write-Error "Public Windows installer regressed below standalone size threshold: $($installer.FullName) ($($installer.Length) bytes)"
exit 1
}
Write-Host "Public installer still valid for release upload: $($installer.Name)"
Write-Host "Installer size: $([math]::Round($installer.Length / 1MB, 1)) MB"
- name: Build MSIX package
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
env:
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
shell: pwsh
run: |
pwsh -File packages/app-core/packaging/msix/build-msix.ps1 `
-BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build") `
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
-Version "${{ needs.prepare.outputs.version }}"
# When building unsigned (skip_codesign=1), the CEF framework ships with
# a pre-existing signature. Injecting version.json into Resources breaks
# that signature (resource hash mismatch). Ad-hoc re-sign the .app so
# the bundle is internally consistent even without a Developer ID cert.
- name: Ad-hoc re-sign unsigned macOS bundle
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign == '1' && steps.build-electrobun-app.outputs.fallback != 'true'
run: |
for app in packages/app-core/platforms/electrobun/artifacts/*.app; do
[ -d "$app" ] || continue
echo "Ad-hoc signing: $app"
codesign --force --deep --sign - "$app" || echo "::warning::Ad-hoc re-sign failed for $app"
done
- name: Verify macOS signature and notarization
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
run: |
shopt -s nullglob
retry_stapler_validate() {
local target="$1"
local attempts="${2:-5}"
local delay_seconds="${3:-15}"
local attempt
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
if xcrun stapler validate "$target"; then
return 0
fi
if [ "$attempt" -lt "$attempts" ]; then
echo "::warning::stapler validate failed for $target (attempt $attempt/$attempts); retrying..."
sleep "$((delay_seconds * attempt))"
fi
done
return 1
}
apps=()
while IFS= read -r app; do
apps+=("$app")
done < <(find -L packages/app-core/platforms/electrobun/artifacts -maxdepth 1 -type d -name "*.app" | sort)
mounted_volume=""
if [ "${#apps[@]}" -eq 0 ]; then
dmgs=(packages/app-core/platforms/electrobun/artifacts/*.dmg)
if [ "${#dmgs[@]}" -eq 0 ]; then
echo "::error::No .app bundle or .dmg found in packages/app-core/platforms/electrobun/artifacts"
exit 1
fi
dmg="${dmgs[0]}"
echo "No .app bundle found in artifacts; mounting DMG: $dmg"
mount_line=""
for attempt in 1 2 3 4 5; do
attach_output="$(hdiutil attach -nobrowse -readonly "$dmg" 2>&1)" && \
mount_line="$(printf '%s\n' "$attach_output" | awk '/\/Volumes\// { print substr($0, index($0, "/Volumes/")); exit }')" && \
[ -n "$mount_line" ] && [ -d "$mount_line" ] && break
echo "::warning::DMG attach attempt $attempt/5 failed"
if [ -n "${attach_output:-}" ]; then
printf '%s\n' "$attach_output"
fi
mount_line=""
sleep 2
done
if [ -z "$mount_line" ]; then
echo "::error::Failed to determine mounted DMG volume"
exit 1
fi
mounted_volume="$mount_line"
trap 'if [ -n "$mounted_volume" ] && [ -d "$mounted_volume" ]; then hdiutil detach "$mounted_volume" >/dev/null 2>&1 || true; fi' EXIT
apps=()
while IFS= read -r app; do
apps+=("$app")
done < <(find "$mounted_volume" -maxdepth 1 -type d -name "*.app" | sort)
if [ "${#apps[@]}" -eq 0 ]; then
echo "::error::No .app bundle found inside mounted DMG"
exit 1
fi
fi
for app in "${apps[@]}"; do
echo "Verifying: $app"
codesign --verify --deep --strict --verbose=2 "$app"
info="$(codesign -dv --verbose=4 "$app" 2>&1 || true)"
echo "$info" | grep -q "Authority=Developer ID Application" || {
echo "::error::Missing Developer ID Application signature on $app"
exit 1
}
# Gatekeeper can reject the standalone staging copy on Intel even
# when the notarized DMG is valid; treat as advisory.
if ! spctl -a -vv --type exec "$app"; then
echo "::warning::Gatekeeper rejected staged app bundle $app; continuing because the notarized DMG is the release artifact."
fi
retry_stapler_validate "$app" 3 10 || echo "::warning::Stapler validation failed (may be expected for .app; DMG stapling is the norm)"
done
found_dmg=0
while IFS= read -r dmg; do
[[ -z "$dmg" ]] && continue
found_dmg=1
echo "Verifying installer: $dmg"
dmg_info="$(codesign -dv --verbose=4 "$dmg" 2>&1 || true)"
echo "$dmg_info"
echo "$dmg_info" | grep -q "Authority=Developer ID Application" || {
echo "::error::Missing Developer ID Application signature on $dmg"
exit 1
}
retry_stapler_validate "$dmg"
done < <(find -L packages/app-core/platforms/electrobun/artifacts -maxdepth 1 -type f -name "*.dmg" | sort)
if [ "$found_dmg" -eq 0 ]; then
echo "::error::No DMG found in packages/app-core/platforms/electrobun/artifacts"
exit 1
fi
- name: Smoke test packaged macOS app
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
continue-on-error: true
env:
SMOKE_DIAGNOSTICS_DIR: ${{ runner.temp }}/eliza-smoke-diagnostics
run: |
STARTUP_TIMEOUT=420 \
LIVENESS_TIMEOUT=10 \
SKIP_BUILD=1 \
SKIP_SIGNATURE_CHECK=1 \
bun run test:desktop:packaged
- name: Collect Windows smoke diagnostics
if: failure() && matrix.platform.os == 'windows'
shell: pwsh
run: |
$diagnosticsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts/windows-smoke-diagnostics"
$appDataRoot = if ($env:ELIZA_TEST_WINDOWS_APPDATA_PATH) { $env:ELIZA_TEST_WINDOWS_APPDATA_PATH } else { $env:APPDATA }
$localAppDataRoot = if ($env:ELIZA_TEST_WINDOWS_LOCALAPPDATA_PATH) { $env:ELIZA_TEST_WINDOWS_LOCALAPPDATA_PATH } else { $env:LOCALAPPDATA }
$wrapperRoots = @(@(
(Join-Path $localAppDataRoot "com.elizaai.eliza"),
(Join-Path $localAppDataRoot "ai.elizaos.Eliza"),
(Join-Path $localAppDataRoot "ai.elizaos.app")
) | Select-Object -Unique)
Write-Host "Diagnostics roots:"
Write-Host " appDataRoot = $appDataRoot"
Write-Host " localAppDataRoot = $localAppDataRoot"
foreach ($wrapperRoot in $wrapperRoots) {
Write-Host " wrapperRoot = $wrapperRoot"
}
Remove-Item $diagnosticsDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $diagnosticsDir | Out-Null
if (Test-Path $appDataRoot) {
Write-Host "--- APPDATA tree ($appDataRoot) ---"
Get-ChildItem -Path $appDataRoot -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.FullName)" }
Write-Host "--- end APPDATA tree ---"
$logFiles = Get-ChildItem -Path $appDataRoot -Recurse -File -Include "*.log" -ErrorAction SilentlyContinue
foreach ($log in $logFiles) {
$relative = $log.FullName.Substring($appDataRoot.Length).TrimStart('\','/')
$safeName = $relative -replace '[\\/:]', '_'
$dest = Join-Path $diagnosticsDir $safeName
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
Copy-Item $log.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
Write-Host "--- $($log.FullName) ---"
Get-Content $log.FullName -Tail 400 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_ }
Write-Host "--- end $($log.FullName) ---"
}
if (-not $logFiles) {
Write-Warning "No *.log files found anywhere under $appDataRoot"
}
} else {
Write-Warning "APPDATA root not found: $appDataRoot"
}
if (Test-Path $localAppDataRoot) {
$localLogFiles = Get-ChildItem -Path $localAppDataRoot -Recurse -File -Include "*.log" -ErrorAction SilentlyContinue
foreach ($log in $localLogFiles) {
$relative = $log.FullName.Substring($localAppDataRoot.Length).TrimStart('\','/')
$safeName = "local_" + ($relative -replace '[\\/:]', '_')
$dest = Join-Path $diagnosticsDir $safeName
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
Copy-Item $log.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
Write-Host "--- $($log.FullName) ---"
Get-Content $log.FullName -Tail 400 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_ }
Write-Host "--- end $($log.FullName) ---"
}
}
foreach ($pattern in @("eliza-windows-smoke-*.state.json", "eliza-windows-smoke-*.events.jsonl", "eliza-startup-trace-*.state.json")) {
$traceGlob = Join-Path $env:RUNNER_TEMP $pattern
$traceFiles = Get-ChildItem -Path $traceGlob -ErrorAction SilentlyContinue
if ($traceFiles) {
foreach ($f in $traceFiles) {
$dest = Join-Path $diagnosticsDir $f.Name
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
Copy-Item $f.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
Write-Host "--- startup-trace: $($f.Name) ---"
Get-Content $f.FullName -Tail 400 | ForEach-Object { Write-Host $_ }
Write-Host "--- end $($f.Name) ---"
}
}
}
foreach ($root in @($wrapperRoots + @("C:\\e", "D:\\a\\_temp\\el"))) {
if (Test-Path $root) {
Get-ChildItem -Path $root -Recurse -File -Filter "startup-session.json" -ErrorAction SilentlyContinue |
ForEach-Object {
$dest = Join-Path $diagnosticsDir ("startup-session-" + ($_.FullName -replace '[\\:]', '_') + ".json")
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
Copy-Item $_.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
Write-Host "--- $($_.FullName) ---"
Get-Content $_.FullName | ForEach-Object { Write-Host $_ }
Write-Host "--- end ---"
}
}
}
foreach ($wrapperRoot in $wrapperRoots) {
if (Test-Path $wrapperRoot) {
Get-ChildItem -Path $wrapperRoot -Recurse -File -Filter "wrapper-diagnostics.json" -ErrorAction SilentlyContinue |
ForEach-Object {
$relativePath = $_.FullName.Substring($wrapperRoot.Length).TrimStart('\\')
$destination = Join-Path $diagnosticsDir $relativePath
$destinationParent = Split-Path -Parent $destination
New-Item -ItemType Directory -Force -Path $destinationParent | Out-Null
Copy-Item $_.FullName -Destination $destination -Force
}
} else {
Write-Warning "Wrapper diagnostics root not found at $wrapperRoot"
}
}
- name: Upload Windows smoke diagnostics
if: failure() && matrix.platform.os == 'windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}-smoke-diagnostics
path: packages/app-core/platforms/electrobun/artifacts/windows-smoke-diagnostics/**
if-no-files-found: warn
retention-days: 14
- name: Upload macOS smoke diagnostics
if: failure() && matrix.platform.os == 'macos'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}-smoke-diagnostics
path: |
${{ runner.temp }}/eliza-smoke-diagnostics/**
packages/app-core/platforms/electrobun/build/**/wrapper-diagnostics.json
if-no-files-found: warn
retention-days: 14
- name: Compress Windows artifacts before upload
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
$canonicalInstallers = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending
if (-not $canonicalInstallers) {
Write-Error "No canonical Windows installer found before compression."
exit 1
}
if ($canonicalInstallers.Count -gt 1) {
Write-Error "Multiple canonical Windows installers found before compression."
$canonicalInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
exit 1
}
$canonicalInstaller = $canonicalInstallers | Select-Object -First 1
$otherSetupExecutables = Get-ChildItem -Path $artifactsDir -File -Filter "*Setup*.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -ne $canonicalInstaller.FullName }
foreach ($extraSetup in $otherSetupExecutables) {
Write-Warning "Removing non-canonical setup executable before upload: $($extraSetup.FullName)"
Remove-Item $extraSetup.FullName -Force -ErrorAction SilentlyContinue
}
$exeFiles = Get-ChildItem -Path $artifactsDir -File -Filter "*.exe" -ErrorAction SilentlyContinue
foreach ($exe in $exeFiles) {
if ($exe.FullName -eq $canonicalInstaller.FullName) {
Write-Host "Keeping canonical Windows installer uncompressed for single-extract downloads: $($exe.Name)"
continue
}
$zipPath = $exe.FullName + ".zip"
Write-Host "Compressing $($exe.Name) -> $($exe.Name).zip"
Compress-Archive -Path $exe.FullName -DestinationPath $zipPath -CompressionLevel Optimal
Remove-Item $exe.FullName -Force
$originalMB = [math]::Round($exe.Length / 1MB, 1)
$compressedMB = [math]::Round((Get-Item $zipPath).Length / 1MB, 1)
Write-Host "Compressed: ${originalMB}MB -> ${compressedMB}MB"
}
- name: Prepare public canary Windows installer artifact
if: matrix.platform.os == 'windows' && needs.prepare.outputs.env == 'canary' && steps.build-electrobun-app.outputs.fallback != 'true'
shell: pwsh
run: |
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
$publicCanaryDir = Join-Path $artifactsDir "public-canary-installer"
Remove-Item $publicCanaryDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $publicCanaryDir | Out-Null
$canonicalInstallers = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending
if ($canonicalInstallers) {
if ($canonicalInstallers.Count -gt 1) {
Write-Error "Multiple canonical Windows installers found for canary artifact publishing."
$canonicalInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
exit 1
}
$canonicalInstaller = $canonicalInstallers | Select-Object -First 1
Copy-Item $canonicalInstaller.FullName -Destination $publicCanaryDir -Force
} else {
$canonicalInstallerZips = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe.zip" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending
if (-not $canonicalInstallerZips) {
Write-Error "No canonical Windows installer (or zip fallback) found for canary artifact publishing."
exit 1
}
if ($canonicalInstallerZips.Count -gt 1) {
Write-Error "Multiple canonical Windows installer zips found for canary artifact publishing."
$canonicalInstallerZips | ForEach-Object { Write-Host " - $($_.FullName)" }
exit 1
}
$canonicalInstallerZip = $canonicalInstallerZips | Select-Object -First 1
Expand-Archive -Path $canonicalInstallerZip.FullName -DestinationPath $publicCanaryDir -Force
}
$publicInstallers = Get-ChildItem -Path $publicCanaryDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending
if (-not $publicInstallers) {
Write-Error "Failed to stage a public Windows installer under $publicCanaryDir"
exit 1
}
if ($publicInstallers.Count -gt 1) {
Write-Error "Canary installer artifact contains multiple public installers."
$publicInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
exit 1
}
$publicInstaller = $publicInstallers | Select-Object -First 1
Write-Host "Prepared public canary installer artifact: $($publicInstaller.FullName)"
- name: Upload public canary installer artifact
if: matrix.platform.os == 'windows' && needs.prepare.outputs.env == 'canary' && steps.build-electrobun-app.outputs.fallback != 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}-public-installer
path: packages/app-core/platforms/electrobun/artifacts/public-canary-installer/*-Setup-*.exe
if-no-files-found: error
retention-days: 30
compression-level: 0
- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}
path: |
packages/app-core/platforms/electrobun/artifacts/*.dmg
packages/app-core/platforms/electrobun/artifacts/*.AppImage
packages/app-core/platforms/electrobun/artifacts/*.deb
packages/app-core/platforms/electrobun/artifacts/*.rpm
packages/app-core/platforms/electrobun/artifacts/*.tar.zst
packages/app-core/platforms/electrobun/artifacts/*.exe
packages/app-core/platforms/electrobun/artifacts/*.exe.zip
packages/app-core/platforms/electrobun/artifacts/*.tar.gz
packages/app-core/platforms/electrobun/artifacts/*-update.json
packages/app-core/platforms/electrobun/artifacts/*.patch
if-no-files-found: error
retention-days: 30
compression-level: 0
- name: Upload MSIX artifact
if: matrix.platform.os == 'windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: electrobun-${{ matrix.platform.artifact-name }}-msix
path: packages/app-core/platforms/electrobun/artifacts/*.msix
if-no-files-found: warn
retention-days: 30
compression-level: 6
release:
name: Create Release
if: >-
${{
(inputs.platform == '' || inputs.platform == 'all') &&
(github.event_name != 'workflow_call' || inputs.publish_release) &&
(github.event_name != 'push' || needs.prepare.outputs.env != 'canary')
}}
needs: [prepare, build]
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
timeout-minutes: 10
permissions:
contents: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
path: release-artifacts
pattern: electrobun-*
merge-multiple: false
- name: List artifacts
run: find release-artifacts -type f | sort 2>/dev/null || echo "No artifacts"
- name: Collect public release files
run: |
mkdir -p release-files
# Public GitHub releases should expose end-user installables, not the
# updater transport files. Windows publishes the standalone Inno Setup
# installer, not the raw Electrobun bootstrap stub.
find release-artifacts -type f \( \
-name "*.dmg" -o \
-name "*.app.tar.gz" -o \
-name "*.AppImage" -o \
-name "*.deb" -o \
-name "*.rpm" -o \
-name "*-Setup-*.exe" -o \
-name "*-Setup-*.exe.zip" -o \
-name "eliza-canary-windows-*.exe.zip" -o \
-name "*Setup*.tar.gz" -o \
-name "*.tar.zst" -o \
-name "*.flatpak" -o \
-name "*.msix" \
\) -exec cp {} release-files/ \; 2>/dev/null || true
# Decompress Windows .exe.zip back to .exe for end-user downloads
# (zip was only used for inter-job artifact transfer to avoid SAS token timeout)
for f in release-files/*-Setup-*.exe.zip; do
[ -f "$f" ] || continue
echo "Decompressing $f"
cd release-files && unzip -o "$(basename "$f")" && rm "$(basename "$f")" && cd ..
done
echo "=== Public release files ==="
ls -lh release-files/ 2>/dev/null || echo "None"
- name: Collect update channel files
run: |
mkdir -p update-channel
find release-artifacts -type f \( \
-name "*.tar.zst" -o \
-name "*.patch" -o \
-name "*-update.json" \
\) -exec cp {} update-channel/ \; 2>/dev/null || true
echo "=== Update channel files ==="
ls -lh update-channel/ 2>/dev/null || echo "None"
- name: Generate checksums
run: |
cd release-files
sha256sum -- * > SHA256SUMS.txt
cat SHA256SUMS.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b
with:
tag_name: ${{ needs.prepare.outputs.tag }}
target_commitish: ${{ needs.prepare.outputs.source_sha }}
name: Eliza ${{ needs.prepare.outputs.tag }}
draft: ${{ inputs.draft || false }}
prerelease: ${{ contains(needs.prepare.outputs.version, '-') }}
generate_release_notes: true
files: release-files/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Upload platform-prefixed update manifests + tarballs to the eliza
# releases host so the Electrobun updater can discover them via the
# flat baseUrl contract in electrobun.config.ts:
# ${baseUrl}/${platformPrefix}-update.json
# ${baseUrl}/${platformPrefix}-${tarballFileName}
# Requires RELEASE_UPLOAD_KEY secret (SSH private key for releases server).
# Skipped for draft releases — test/preview builds must not pollute the production update channel.
# TODO: configure releases.elizaos.ai (or chosen release host) and update
# the rsync target / known_hosts entry below before enabling.
- name: Upload update channel files to release host
if: ${{ !inputs.draft }}
env:
RELEASE_UPLOAD_KEY: ${{ secrets.RELEASE_UPLOAD_KEY }}
RELEASE_HOST_FINGERPRINT: ${{ secrets.RELEASE_HOST_FINGERPRINT }}
run: |
if [ -z "$RELEASE_UPLOAD_KEY" ]; then
echo "No RELEASE_UPLOAD_KEY secret — skipping upload"
exit 0
fi
echo "$RELEASE_UPLOAD_KEY" > /tmp/release_key
chmod 600 /tmp/release_key
# Pin the host fingerprint from RELEASE_HOST_FINGERPRINT so SSH
# verifies the server identity without disabling host-key checking.
# To rotate: run `ssh-keyscan -H <release-host>` and update the secret.
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "$RELEASE_HOST_FINGERPRINT" >> ~/.ssh/known_hosts
# Electrobun resolves update metadata and tarballs at the flat
# release root, keyed by platform prefix, not inside version folders.
rsync -av --delete-after \
-e "ssh -i /tmp/release_key" \
update-channel/ \
releases@releases.elizaos.ai:/var/www/releases/
rm -f /tmp/release_key
publish-browser-companions:
name: Publish Agent Browser Bridge companions
if: ${{ (github.event_name != 'workflow_call' || inputs.publish_release) && needs.build-browser-companions.outputs.packaged == 'true' && needs.release.result == 'success' }}
needs: [prepare, build-browser-companions, release]
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download Agent Browser Bridge artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
path: browser-bridge-release
pattern: browser-bridge-*
merge-multiple: false
- name: List Agent Browser Bridge artifacts
run: find browser-bridge-release -type f | sort 2>/dev/null || echo "No Agent Browser Bridge artifacts"
- name: Attach Agent Browser Bridge assets to GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
find browser-bridge-release -type f \
\( \
-name "browser-bridge-chrome-v*.zip" -o \
-name "browser-bridge-safari-v*.zip" -o \
-name "browser-bridge-release-manifest-v*.json" \
\) \
-print0 | xargs -0 -r gh release upload "${{ needs.prepare.outputs.tag }}" --repo "$GH_REPO" --clobber
ota-publish:
name: Publish OTA Channel Manifest
if: ${{ success() && needs.release.result == 'success' }}
needs: [prepare, release]
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- name: Generate channel manifest
env:
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
BUILD_ENV: ${{ needs.prepare.outputs.env }}
run: |
CHANNEL="${BUILD_ENV}"
UPDATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
BASE_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}"
cat > "latest-${CHANNEL}.json" <<JSON
{
"channel": "${CHANNEL}",
"version": "${VERSION}",
"updatedAt": "${UPDATED_AT}",
"platforms": {
"macos-arm64": "${BASE_URL}/macos-arm64-update.json",
"macos-x64": "${BASE_URL}/macos-x64-update.json",
"windows-x64": "${BASE_URL}/windows-x64-update.json",
"linux-x64": "${BASE_URL}/linux-x64-update.json"
}
}
JSON
echo "Channel manifest:"
cat "latest-${CHANNEL}.json"
- name: Attach channel manifest to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
BUILD_ENV: ${{ needs.prepare.outputs.env }}
run: |
CHANNEL="${BUILD_ENV}"
if ! gh release view "$TAG" >/dev/null 2>&1; then
echo "::warning::No GitHub release found for $TAG; skipping OTA manifest upload."
exit 0
fi
gh release upload "$TAG" "latest-${CHANNEL}.json" --clobber
echo "OTA manifest attached: latest-${CHANNEL}.json"
echo "Update check URL: https://github.com/${{ github.repository }}/releases/download/${TAG}/latest-${CHANNEL}.json"
- name: Upload via SSH (optional)
if: ${{ env.RELEASE_UPLOAD_KEY != '' }}
env:
RELEASE_UPLOAD_KEY: ${{ secrets.RELEASE_UPLOAD_KEY }}
RELEASE_HOST_FINGERPRINT: ${{ secrets.RELEASE_HOST_FINGERPRINT }}
TAG: ${{ needs.prepare.outputs.tag }}
BUILD_ENV: ${{ needs.prepare.outputs.env }}
run: |
mkdir -p ~/.ssh
echo "$RELEASE_UPLOAD_KEY" > ~/.ssh/release_key
chmod 600 ~/.ssh/release_key
if [[ -n "$RELEASE_HOST_FINGERPRINT" ]]; then
echo "$RELEASE_HOST_FINGERPRINT" >> ~/.ssh/known_hosts
fi
CHANNEL="${BUILD_ENV}"
DEST_PATH="/srv/releases/elizaos/${TAG}/"
rsync -av --mkpath \
-e "ssh -i ~/.ssh/release_key -o StrictHostKeyChecking=yes" \
"latest-${CHANNEL}.json" \
"releases@${RELEASE_HOST}:${DEST_PATH}"
rm -f ~/.ssh/release_key