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
697 lines
33 KiB
YAML
697 lines
33 KiB
YAML
name: Mobile Build Smoke Test
|
|
|
|
on:
|
|
# Reusable entry for the scheduled exhaustive develop lane (#12342).
|
|
workflow_call:
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened, synchronize, reopened, ready_for_review, labeled]
|
|
workflow_dispatch:
|
|
inputs:
|
|
cloud_api_base:
|
|
description: "Eliza Cloud API base for the iOS cloud runtime-mode smoke (defaults to the staging sandbox var, then prod)"
|
|
required: false
|
|
default: ""
|
|
schedule:
|
|
# Nightly iOS local (full-Bun on-device engine) lane (#13578). This mode
|
|
# is too heavy for the PR path — a full-Bun engine build, a real ~1.3GB
|
|
# GGUF, and CPU inference in the simulator — so it runs on a schedule and
|
|
# gates that nightly run instead of every pull request.
|
|
- cron: "0 7 * * *"
|
|
|
|
concurrency:
|
|
group: mobile-smoke-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
env:
|
|
BUN_VERSION: "canary"
|
|
|
|
# Default to least privilege. Override per-job where needed.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
changes:
|
|
name: Classify changed paths
|
|
# Path classifier is a git-diff + node script: no self-hosted resources.
|
|
# Keep it on GitHub-hosted so a drained hetzner fleet cannot leave it
|
|
# queued indefinitely and pile up runs (#13617/#8501).
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 10
|
|
outputs:
|
|
ios: ${{ steps.filter.outputs.ios }}
|
|
android: ${{ steps.filter.outputs.android }}
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
fetch-depth: 0
|
|
submodules: false
|
|
|
|
- name: Setup Node.js for path gate
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: "24"
|
|
|
|
- name: Determine affected mobile smoke surface
|
|
id: filter
|
|
shell: bash
|
|
env:
|
|
PR_LABELS: ${{ github.event_name == 'pull_request' && join(github.event.pull_request.labels.*.name, ',') || '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
node packages/scripts/ci-path-gate.mjs \
|
|
--config mobile \
|
|
--event "${{ github.event_name }}" \
|
|
--base "${{ github.event.pull_request.base.sha }}" \
|
|
--head "${{ github.event.pull_request.head.sha }}" \
|
|
--labels "$PR_LABELS" \
|
|
--output "$GITHUB_OUTPUT" \
|
|
--summary "$GITHUB_STEP_SUMMARY"
|
|
|
|
build-ios:
|
|
name: iOS Simulator Build
|
|
needs: changes
|
|
if: github.event_name != 'pull_request' || needs.changes.outputs.ios == 'true'
|
|
runs-on: macos-15
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
# This workflow validates the app-core mobile project wiring, not
|
|
# native inference submodule health. Keep checkout source-clean and
|
|
# let setup-bun-workspace/postinstall initialize only the submodules
|
|
# it actually needs.
|
|
submodules: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Setup workspace dependencies
|
|
uses: ./.github/actions/setup-bun-workspace
|
|
with:
|
|
bun-version: ${{ env.BUN_VERSION }}
|
|
install-native-deps: "false"
|
|
install-command: bun install --ignore-scripts --no-frozen-lockfile
|
|
run-postinstall: "true"
|
|
skip-avatar-clone: "true"
|
|
no-vision-deps: "true"
|
|
|
|
- name: Generate protobuf types
|
|
# Restores the step that was on the last green Mobile Build Smoke
|
|
# run (52ab686a, 2026-05-09). Without these generated types in
|
|
# `packages/core/src/types/generated/`, `@elizaos/core`'s build
|
|
# fails downstream, which cascades into `@elizaos/shared` and
|
|
# vite config resolution.
|
|
run: |
|
|
if [ -d packages/schemas ] && [ -f packages/schemas/buf.gen.yaml ] && [ ! -d packages/core/src/types/generated ]; then
|
|
cd packages/schemas
|
|
node node_modules/@bufbuild/buf/install.js 2>/dev/null || true
|
|
bunx @bufbuild/buf@1.67.0 generate
|
|
fi
|
|
|
|
- name: Build Vite config package deps
|
|
# vite.config.ts imports dist-only workspace packages
|
|
# (`@elizaos/shared` and `@elizaos/ui/config/app-config`), and the
|
|
# app aliases plugin-browser source which imports `@elizaos/vault`.
|
|
# Build those package chains before Vite evaluates/bundles the app.
|
|
run: bun turbo run build --filter=@elizaos/ui --filter=@elizaos/vault
|
|
|
|
- name: Build iOS simulator app
|
|
run: node --max-old-space-size=8192 packages/app-core/scripts/run-mobile-build.mjs ios
|
|
env:
|
|
LANG: en_US.UTF-8
|
|
# The simulator smokes drive a loopback host agent. Use the direct
|
|
# developer iOS lane; App Store/cloud builds intentionally reject
|
|
# cleartext loopback and are covered by release workflows.
|
|
ELIZA_BUILD_VARIANT: "direct"
|
|
ELIZA_RELEASE_AUTHORITY: "developer-toolchain"
|
|
VITE_ELIZA_IOS_ALLOW_SIMULATOR_LOOPBACK: "1"
|
|
ELIZA_IOS_FULL_BUN_ENGINE: "0"
|
|
ELIZA_IOS_BUILD_DESTINATION: "generic/platform=iOS Simulator"
|
|
ELIZA_IOS_BUILD_SDK: "iphonesimulator"
|
|
ELIZA_MOBILE_BUILD_TIMING_OUT: ${{ runner.temp }}/ios-simulator-build-timing.json
|
|
ELIZA_MOBILE_BUILD_TIMING_TARGET: ios-ipa
|
|
NODE_OPTIONS: "--max-old-space-size=8192"
|
|
|
|
- name: Check iOS simulator build-time budget
|
|
run: |
|
|
bun run --cwd packages/app perf:startup-budget -- \
|
|
--build-timing "$RUNNER_TEMP/ios-simulator-build-timing.json" \
|
|
--out "$RUNNER_TEMP/ios-simulator-build-budget-report.json"
|
|
|
|
- name: Upload iOS simulator build budget report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-simulator-build-budget
|
|
path: |
|
|
${{ runner.temp }}/ios-simulator-build-timing.json
|
|
${{ runner.temp }}/ios-simulator-build-budget-report.json
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Verify .app exists
|
|
run: |
|
|
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "App.app" -path "*/Debug-iphonesimulator/*" | head -1)
|
|
if [ -z "$APP_PATH" ]; then
|
|
echo "ERROR: App.app not found in DerivedData"
|
|
exit 1
|
|
fi
|
|
echo "Built app at: $APP_PATH"
|
|
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist")
|
|
echo "Bundle ID: $BUNDLE_ID"
|
|
# Bundle ID source of truth is packages/app/app.config.ts (read
|
|
# by run-mobile-build.mjs via regex). Current value is
|
|
# `app.eliza` since commit d042ed57b5 renamed it from the
|
|
# legacy `ai.elizaos.app`. The two prior ids remain accepted
|
|
# because release branches and downstream forks may not have
|
|
# rebased onto the rename yet.
|
|
case "$BUNDLE_ID" in
|
|
app.eliza|ai.elizaos.app|ai.elizaos.Eliza) ;;
|
|
*)
|
|
echo "ERROR: Unexpected bundle ID: $BUNDLE_ID"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
- name: Verify staged renderer is the freshly built one (no stale UI, #9309)
|
|
run: |
|
|
# The staged iOS public/ must carry exactly the renderer just built in
|
|
# packages/app/dist (build stamp match). Fails loudly on stale/missing UI.
|
|
node packages/app-core/scripts/verify-ondevice-artifact.mjs --platform ios
|
|
# The renderer build stamp must also be baked into the built .app bundle.
|
|
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "App.app" -path "*/Debug-iphonesimulator/*" | head -1)
|
|
if [ ! -f "$APP_PATH/public/eliza-renderer-build.json" ]; then
|
|
echo "ERROR: built .app is missing the renderer build stamp at public/eliza-renderer-build.json"
|
|
exit 1
|
|
fi
|
|
echo "Renderer build stamp present in built .app:"
|
|
cat "$APP_PATH/public/eliza-renderer-build.json"
|
|
|
|
- name: Run iOS onboarding -> home simulator smoke
|
|
run: |
|
|
set -euo pipefail
|
|
node packages/app/scripts/ios-onboarding-smoke.mjs
|
|
|
|
- name: Upload iOS onboarding evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-onboarding-to-home
|
|
path: packages/app/test-results/ios-onboarding-to-home/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Run iOS native attachment smoke (#10936)
|
|
# Evidence lane for the content-addressed media + native
|
|
# Filesystem/Share path. Promoted to a hard-gating step (#13576):
|
|
# a regression in the media-store / Filesystem / Share path now
|
|
# turns Mobile Build Smoke red instead of self-skipping to green.
|
|
run: |
|
|
set -euo pipefail
|
|
node packages/app/scripts/ios-attachment-smoke.mjs
|
|
|
|
- name: Upload iOS native attachment evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-attachment-smoke
|
|
path: packages/app/test-results/ios-attachment-smoke/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Run iOS local-chat simulator smoke (#9943)
|
|
# Wire the existing iOS chat device smoke into CI. mobile-local-chat-smoke
|
|
# drives a real chat turn through the installed simulator app (the iOS
|
|
# self-driving-WebView mechanism — WKWebView is not CDP/Playwright-drivable,
|
|
# so this is the only viable iOS device-chat coverage). It previously ran
|
|
# in ZERO CI lanes (issue #9943 item 5). Promoted to a hard-gating
|
|
# step (#13576): a regression in iOS chat send/receive through the
|
|
# app-core API now turns Mobile Build Smoke red instead of
|
|
# self-skipping to green.
|
|
run: |
|
|
set -euo pipefail
|
|
node packages/app/scripts/mobile-local-chat-smoke.mjs \
|
|
--platform ios --require-installed --start-host-agent
|
|
|
|
- name: Upload iOS local-chat evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-local-chat
|
|
path: packages/app/test-results/ios-local-chat/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Run Mobile Safari chat-sheet visual smoke (#15666)
|
|
# run-chat-sheet-mobile-safari-smoke.mjs builds the WebKit chat-sheet
|
|
# fixture via the Playwright smoke, serves it over loopback HTTP, opens
|
|
# it in real Mobile Safari on the booted simulator, and asserts a
|
|
# non-blank screen capture. Wired here because this is the only
|
|
# PR-path lane with an iOS Simulator available, and the ui-e2e
|
|
# runner-coverage ratchet (packages/scripts/__tests__/
|
|
# ui-e2e-runner-coverage.test.ts) requires every packages/ui __e2e__
|
|
# runner to be invoked by a workflow leg.
|
|
run: |
|
|
set -euo pipefail
|
|
bunx playwright install webkit
|
|
bun run --cwd packages/ui test:chat-sheet-mobile-safari-smoke
|
|
|
|
- name: Upload Mobile Safari chat-sheet evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-mobile-safari-chat-sheet
|
|
path: packages/ui/src/components/shell/__e2e__/output-mobile-safari/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Verify required Info.plist keys for background HealthKit
|
|
# Background HealthKit + BGTaskScheduler integration is in
|
|
# progress: no plist generator is configured yet (no
|
|
# capacitor.config.ts customization, no run-mobile-build.mjs
|
|
# writer for these keys), so until that lands the verify is a
|
|
# non-blocking warning. Flip back to a hard fail when the
|
|
# generator pipeline lands.
|
|
continue-on-error: true
|
|
run: |
|
|
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "App.app" -path "*/Debug-iphonesimulator/*" | head -1)
|
|
INFO_PLIST="$APP_PATH/Info.plist"
|
|
for key in NSHealthShareUsageDescription BGTaskSchedulerPermittedIdentifiers UIBackgroundModes; do
|
|
if ! /usr/libexec/PlistBuddy -c "Print :$key" "$INFO_PLIST" >/dev/null 2>&1; then
|
|
echo "::warning::Info.plist missing required key: $key (TODO: wire up plist customization for background HealthKit)"
|
|
/usr/libexec/PlistBuddy -c "Print" "$INFO_PLIST" | head -80
|
|
exit 1
|
|
fi
|
|
done
|
|
# Confirm the sleep-refresh task identifier is whitelisted so
|
|
# BGTaskScheduler.register actually succeeds on device.
|
|
if ! /usr/libexec/PlistBuddy -c "Print :BGTaskSchedulerPermittedIdentifiers" "$INFO_PLIST" \
|
|
| grep -q "ai.eliza.mobile-signals.sleep-refresh"; then
|
|
echo "::warning::BGTaskSchedulerPermittedIdentifiers missing ai.eliza.mobile-signals.sleep-refresh"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify Fastlane config landed in packages/app/ios
|
|
# Capacitor's iOS project ends up at packages/app/ios/ once
|
|
# run-mobile-build.mjs runs. overlayIosNativeFiles() copies
|
|
# the Fastlane config there. Skip when the dir hasn't been
|
|
# populated yet (Phase D mobile pipeline isn't always run in
|
|
# smoke).
|
|
continue-on-error: true
|
|
run: |
|
|
if [ ! -d packages/app/ios ]; then
|
|
echo "::warning::packages/app/ios/ not present; skipping Fastlane config verify"
|
|
exit 0
|
|
fi
|
|
for f in packages/app/ios/Gemfile packages/app/ios/fastlane/Fastfile packages/app/ios/fastlane/Appfile packages/app/ios/fastlane/Matchfile; do
|
|
if [ ! -f "$f" ]; then
|
|
echo "::warning::$f missing. overlayIosNativeFiles() should have copied Fastlane config here so CI can call \`fastlane beta|release\`."
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "Fastlane config present at packages/app/ios/."
|
|
|
|
build-ios-local:
|
|
name: iOS Local (full-Bun) Simulator Smoke
|
|
# Nightly / on-demand only — never on the PR path. The full-Bun on-device
|
|
# engine build + real GGUF + CPU inference is far too heavy to gate every
|
|
# pull request, so the local runtime mode gates the scheduled run instead.
|
|
# This closes the coverage gap called out in #13578: remote mode is
|
|
# CI-blocking, but local (full-Bun) mode had no unattended lane. Mirrors
|
|
# the remote build-ios job, swapping the direct/loopback host-agent smoke
|
|
# for the on-device full-Bun engine smoke (no host agent — the engine runs
|
|
# in-process and the smoke exchanges the request/result via Preferences).
|
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
runs-on: macos-15
|
|
timeout-minutes: 90
|
|
env:
|
|
# A real, small on-device GGUF — the same eliza-1 e2b text model the
|
|
# Android live smoke stages. mobile-local-chat-smoke.mjs copies whatever
|
|
# ELIZA_IOS_FULL_BUN_SMOKE_MODEL_PATH points at into the sim container and
|
|
# registers it under the `eliza-1-2b` id, capping the context to 4096 so
|
|
# model load + first reply stay fast on a simulator.
|
|
IOS_FULL_BUN_SMOKE_MODEL_URL: "https://huggingface.co/elizaos/eliza-1/resolve/main/bundles/e2b/text/eliza-1-e2b-32k.gguf?download=true"
|
|
IOS_FULL_BUN_SMOKE_MODEL_FILE: "eliza-1-e2b-32k.gguf"
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
submodules: false
|
|
|
|
# The full-Bun on-device engine builds the MTP-forked llama.cpp kernels
|
|
# (run-mobile-build.mjs ios-local with ELIZA_IOS_FULL_BUN_ENGINE=1), which
|
|
# requires the llama.cpp fork checkout. `submodules: false` above keeps the
|
|
# checkout source-clean, so init just that submodule (not the full recursive
|
|
# tree) before the build — build-llama-cpp-mtp.mjs otherwise aborts with
|
|
# "no llama.cpp fork checkout found".
|
|
- name: Initialize llama.cpp fork submodule (full-Bun MTP kernels)
|
|
run: git submodule update --init --recursive plugins/plugin-local-inference/native/llama.cpp
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Setup workspace dependencies
|
|
uses: ./.github/actions/setup-bun-workspace
|
|
with:
|
|
bun-version: ${{ env.BUN_VERSION }}
|
|
install-native-deps: "false"
|
|
install-command: bun install --ignore-scripts --no-frozen-lockfile
|
|
run-postinstall: "true"
|
|
skip-avatar-clone: "true"
|
|
no-vision-deps: "true"
|
|
|
|
- name: Generate protobuf types
|
|
run: |
|
|
if [ -d packages/schemas ] && [ -f packages/schemas/buf.gen.yaml ] && [ ! -d packages/core/src/types/generated ]; then
|
|
cd packages/schemas
|
|
node node_modules/@bufbuild/buf/install.js 2>/dev/null || true
|
|
bunx @bufbuild/buf@1.67.0 generate
|
|
fi
|
|
|
|
- name: Build Vite config package deps
|
|
run: bun turbo run build --filter=@elizaos/ui --filter=@elizaos/vault
|
|
|
|
- name: Cache full-Bun smoke GGUF
|
|
id: model-cache
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
|
with:
|
|
path: ~/.cache/eliza-ios-full-bun-smoke
|
|
key: ios-full-bun-smoke-model-${{ env.IOS_FULL_BUN_SMOKE_MODEL_FILE }}
|
|
|
|
- name: Download full-Bun smoke GGUF (cache miss)
|
|
if: steps.model-cache.outputs.cache-hit != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p "$HOME/.cache/eliza-ios-full-bun-smoke"
|
|
curl -fL --retry 3 --retry-all-errors \
|
|
-o "$HOME/.cache/eliza-ios-full-bun-smoke/$IOS_FULL_BUN_SMOKE_MODEL_FILE" \
|
|
"$IOS_FULL_BUN_SMOKE_MODEL_URL"
|
|
ls -la "$HOME/.cache/eliza-ios-full-bun-smoke/"
|
|
|
|
- name: Build iOS simulator app (full-Bun local runtime)
|
|
# Mirrors `build:ios:local:sim` (run-mobile-build.mjs ios-local with
|
|
# ELIZA_IOS_FULL_BUN_ENGINE=1). The remote lane builds the same target
|
|
# with the engine disabled (ELIZA_IOS_FULL_BUN_ENGINE=0); flipping it on
|
|
# is exactly the coverage this lane adds.
|
|
run: node --max-old-space-size=8192 packages/app-core/scripts/run-mobile-build.mjs ios-local
|
|
env:
|
|
LANG: en_US.UTF-8
|
|
ELIZA_BUILD_VARIANT: "direct"
|
|
ELIZA_RELEASE_AUTHORITY: "developer-toolchain"
|
|
VITE_ELIZA_IOS_ALLOW_SIMULATOR_LOOPBACK: "1"
|
|
ELIZA_IOS_FULL_BUN_ENGINE: "1"
|
|
ELIZA_IOS_BUILD_DESTINATION: "generic/platform=iOS Simulator"
|
|
ELIZA_IOS_BUILD_SDK: "iphonesimulator"
|
|
ELIZA_MOBILE_BUILD_TIMING_OUT: ${{ runner.temp }}/ios-full-bun-build-timing.json
|
|
ELIZA_MOBILE_BUILD_TIMING_TARGET: ios-ipa
|
|
NODE_OPTIONS: "--max-old-space-size=8192"
|
|
|
|
- name: Check iOS full-Bun build-time budget
|
|
run: |
|
|
bun run --cwd packages/app perf:startup-budget -- \
|
|
--build-timing "$RUNNER_TEMP/ios-full-bun-build-timing.json" \
|
|
--out "$RUNNER_TEMP/ios-full-bun-build-budget-report.json"
|
|
|
|
- name: Upload iOS full-Bun build budget report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-full-bun-build-budget
|
|
path: |
|
|
${{ runner.temp }}/ios-full-bun-build-timing.json
|
|
${{ runner.temp }}/ios-full-bun-build-budget-report.json
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Verify .app exists
|
|
run: |
|
|
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "App.app" -path "*/Debug-iphonesimulator/*" | head -1)
|
|
if [ -z "$APP_PATH" ]; then
|
|
echo "ERROR: App.app not found in DerivedData"
|
|
exit 1
|
|
fi
|
|
echo "Built app at: $APP_PATH"
|
|
|
|
- name: Verify staged renderer is the freshly built one (no stale UI, #9309)
|
|
run: |
|
|
node packages/app-core/scripts/verify-ondevice-artifact.mjs --platform ios
|
|
|
|
- name: Boot simulator and install the full-Bun app
|
|
run: |
|
|
set -euo pipefail
|
|
# Pick an available iPhone simulator; boot it and wait for readiness.
|
|
UDID=$(xcrun simctl list devices available -j \
|
|
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const j=JSON.parse(s);let u="";for(const runtime of Object.keys(j.devices)){if(!/iOS/.test(runtime))continue;for(const dev of j.devices[runtime]){if(dev.isAvailable&&/iPhone/.test(dev.name)){u=dev.udid;break}}if(u)break}process.stdout.write(u)})')
|
|
if [ -z "$UDID" ]; then
|
|
echo "ERROR: no available iPhone simulator found"
|
|
xcrun simctl list devices available
|
|
exit 1
|
|
fi
|
|
echo "Booting simulator $UDID"
|
|
xcrun simctl boot "$UDID" || true
|
|
xcrun simctl bootstatus "$UDID" -b
|
|
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "App.app" -path "*/Debug-iphonesimulator/*" | head -1)
|
|
echo "Installing $APP_PATH"
|
|
xcrun simctl install "$UDID" "$APP_PATH"
|
|
|
|
- name: Run iOS full-Bun local-chat simulator smoke (#13578)
|
|
# Gating check for the nightly lane: build the on-device full-Bun
|
|
# engine, stage the real GGUF, and assert the exact on-device reply
|
|
# (`ios smoke model works`). No `continue-on-error` — a regression in
|
|
# engine boot, the on-device model registry, or first-reply generation
|
|
# fails the scheduled run.
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p packages/app/test-results/ios-full-bun-local-chat
|
|
# Point the smoke at the cached GGUF via an absolute path it can stat.
|
|
export ELIZA_IOS_FULL_BUN_SMOKE_MODEL_PATH="$HOME/.cache/eliza-ios-full-bun-smoke/$IOS_FULL_BUN_SMOKE_MODEL_FILE"
|
|
if [ ! -f "$ELIZA_IOS_FULL_BUN_SMOKE_MODEL_PATH" ]; then
|
|
echo "ERROR: staged smoke model missing at $ELIZA_IOS_FULL_BUN_SMOKE_MODEL_PATH"
|
|
exit 1
|
|
fi
|
|
node packages/app/scripts/mobile-local-chat-smoke.mjs \
|
|
--platform ios --require-installed --ios-select-local --ios-full-bun-smoke \
|
|
2>&1 | tee packages/app/test-results/ios-full-bun-local-chat/smoke.log
|
|
|
|
- name: Upload iOS full-Bun local-chat evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-full-bun-local-chat
|
|
path: packages/app/test-results/ios-full-bun-local-chat/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
ios-cloud-mode:
|
|
name: iOS Cloud Runtime-Mode Smoke
|
|
# Nightly / on-demand only — never on the PR path. Provisioning a real Eliza
|
|
# Cloud agent (Hetzner-backed) is slow and costs org credit, so the cloud
|
|
# runtime mode gates the scheduled run alongside the full-Bun local lane.
|
|
# This closes the third row of the #13578 coverage matrix: remote mode is
|
|
# PR-blocking and local (full-Bun) mode gates nightly, but cloud mode had no
|
|
# unattended lane at all — the XCUITest cloud-onboarding path XCTSkips at the
|
|
# OAuth wall without a device Cloud session, and cloud-provisioning-e2e.mjs
|
|
# (the programmatic cloud runtime assertion) was only reachable behind the
|
|
# unwired `ios-e2e.mjs --cloud`. cloud-provisioning-e2e.mjs has no workspace
|
|
# imports (node built-ins + fetch only) and needs no build/simulator, so the
|
|
# cloud runtime-mode gate is a fast ubuntu job seeded with a real Cloud
|
|
# credential rather than a browser OAuth leg.
|
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 30
|
|
env:
|
|
# Headless session seed (#13578 done-when #2): a real Cloud credential
|
|
# injected from the org secret, bypassing the interactive device-code /
|
|
# OAuth browser leg that XCTSkips on the simulator. ELIZACLOUD_API_KEY is
|
|
# the same secret launch-hardening-8756.yml uses for its fresh-agent probe.
|
|
ELIZA_CLOUD_AUTH_TOKEN: ${{ secrets.ELIZACLOUD_API_KEY }}
|
|
# ORIGIN ONLY — cloud-provisioning-e2e.mjs appends `/api/v1/...` itself, so
|
|
# this must NOT carry an `/api/v1` suffix (unlike the sandbox-live-smoke
|
|
# `_URL` var, which does). Overridable via workflow_dispatch to target a
|
|
# throwaway org; defaults to production Cloud otherwise.
|
|
ELIZA_CLOUD_API_BASE: ${{ github.event.inputs.cloud_api_base || 'https://api.elizacloud.ai' }}
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
submodules: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Run iOS cloud runtime-mode provisioning smoke (#13578)
|
|
# Gating check for the cloud runtime mode. With the seed present this is a
|
|
# HARD gate (no continue-on-error): a regression in cloud agent creation,
|
|
# Hetzner provisioning, or the provisioned runtime never answering fails
|
|
# the scheduled run. cloud-provisioning-e2e.mjs provisions a real agent,
|
|
# waits for its runtime URL, and probes /api/status|health|me — the same
|
|
# programmatic cloud path the iOS `ios-e2e.mjs --cloud` route calls. When
|
|
# the secret is absent (forks without secret access) the lane records an
|
|
# explicit skip in the step summary rather than silently passing green.
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "${ELIZA_CLOUD_AUTH_TOKEN:-}" ]; then
|
|
{
|
|
echo "### iOS cloud runtime-mode smoke"
|
|
echo ""
|
|
echo "skipped: missing ELIZACLOUD_API_KEY secret (no Cloud session seed available)"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
echo "iOS cloud runtime-mode smoke skipped: missing ELIZACLOUD_API_KEY secret"
|
|
exit 0
|
|
fi
|
|
mkdir -p packages/app/test-results/ios-cloud-mode
|
|
node packages/app/scripts/cloud-provisioning-e2e.mjs \
|
|
--fresh-agent \
|
|
--agent-name "Eliza iOS Cloud Runtime-Mode Smoke" \
|
|
--report packages/app/test-results/ios-cloud-mode/cloud-provisioning.json \
|
|
2>&1 | tee packages/app/test-results/ios-cloud-mode/smoke.log
|
|
|
|
- name: Upload iOS cloud runtime-mode evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-cloud-mode
|
|
path: packages/app/test-results/ios-cloud-mode/**
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
build-android:
|
|
name: Android Debug Build
|
|
needs: changes
|
|
if: github.event_name != 'pull_request' || needs.changes.outputs.android == 'true'
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
# Android cloud-debug smoke sets ELIZA_ANDROID_SKIP_FORK_LLAMA_LIB=1
|
|
# below, so recursive native inference submodules are not part of this
|
|
# contract. Avoid failing app smoke on optional nested native pins.
|
|
submodules: false
|
|
|
|
- name: Setup Java 21
|
|
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287
|
|
with:
|
|
distribution: temurin
|
|
java-version: 21
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Setup Android SDK
|
|
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699
|
|
with:
|
|
packages: "platform-tools platforms;android-34 build-tools;34.0.0"
|
|
|
|
- name: Setup workspace dependencies
|
|
uses: ./.github/actions/setup-bun-workspace
|
|
with:
|
|
bun-version: ${{ env.BUN_VERSION }}
|
|
install-command: bun install --ignore-scripts --no-frozen-lockfile
|
|
install-native-deps: "false"
|
|
run-postinstall: "true"
|
|
skip-avatar-clone: "true"
|
|
no-vision-deps: "true"
|
|
|
|
- name: Generate protobuf types
|
|
# Restores the step that was on the last green Mobile Build Smoke
|
|
# run (52ab686a, 2026-05-09). Without these generated types in
|
|
# `packages/core/src/types/generated/`, `@elizaos/core`'s build
|
|
# fails downstream, which cascades into `@elizaos/shared` and
|
|
# vite config resolution.
|
|
run: |
|
|
if [ -d packages/schemas ] && [ -f packages/schemas/buf.gen.yaml ] && [ ! -d packages/core/src/types/generated ]; then
|
|
cd packages/schemas
|
|
node node_modules/@bufbuild/buf/install.js 2>/dev/null || true
|
|
bunx @bufbuild/buf@1.67.0 generate
|
|
fi
|
|
|
|
- name: Build Vite config package deps
|
|
# vite.config.ts imports dist-only workspace packages
|
|
# (`@elizaos/shared` and `@elizaos/ui/config/app-config`), and the
|
|
# app aliases plugin-browser source which imports `@elizaos/vault`.
|
|
# Build those package chains before Vite evaluates/bundles the app.
|
|
run: bun turbo run build --filter=@elizaos/ui --filter=@elizaos/vault
|
|
|
|
- name: Accept Android SDK licenses
|
|
run: yes | sdkmanager --licenses >/dev/null 2>&1 || true
|
|
|
|
# Build the mobile agent bundle (Phase D) before Capacitor's
|
|
# Android sync needs it. The bundler dedupes @elizaos/core,
|
|
# @elizaos/shared, and @elizaos/plugin-sql to their src trees, so
|
|
# the workspace plugin packages have to be present in
|
|
# node_modules. setup-bun-workspace handles install; we just kick
|
|
# off the bundle here.
|
|
- name: Build mobile agent bundle
|
|
run: bun run --cwd packages/agent build:mobile
|
|
|
|
# Guard against the May-5 Bun.build TLA regression that required
|
|
# hot-patching `s/init_eliza()/await init_eliza()/` on every device
|
|
# bundle. The fix is in 12bfccb481 (lazy plugin-sql resolution from
|
|
# ensureCoreStaticPluginsRegistered); a regression would re-emit
|
|
# `var init_eliza = __esm(async () => {...})`. See
|
|
# docs/porting/on-device-quantization-porting-plan.md §"Current
|
|
# state on the AOSP image" for context.
|
|
- name: Verify init_eliza is sync (no TLA regression)
|
|
run: |
|
|
BUNDLE=packages/agent/dist-mobile/agent-bundle.js
|
|
if grep -qE 'var init_eliza\b[^=]*= __esm\(async ' "$BUNDLE"; then
|
|
echo "::error::init_eliza re-emitted as async __esm — Bun.build cross-module TLA regression"
|
|
exit 1
|
|
fi
|
|
echo "init_eliza sync emit confirmed; no TLA regression"
|
|
|
|
- name: Build Android cloud debug app
|
|
run: node --max-old-space-size=8192 packages/app-core/scripts/run-mobile-build.mjs android-cloud-debug
|
|
env:
|
|
NODE_OPTIONS: "--max-old-space-size=8192"
|
|
# This smoke job intentionally installs no native deps.
|
|
# Real local/AOSP Android builds still fail if fork llama libs are
|
|
# missing; the opt-out only lets CI validate Capacitor/Gradle wiring.
|
|
ELIZA_ANDROID_SKIP_FORK_LLAMA_LIB: "1"
|
|
ELIZA_MOBILE_BUILD_TIMING_OUT: ${{ runner.temp }}/android-cloud-debug-build-timing.json
|
|
ELIZA_MOBILE_BUILD_TIMING_TARGET: android-apk
|
|
|
|
- name: Check Android cloud-debug build-time budget
|
|
run: |
|
|
bun run --cwd packages/app perf:startup-budget -- \
|
|
--build-timing "$RUNNER_TEMP/android-cloud-debug-build-timing.json" \
|
|
--out "$RUNNER_TEMP/android-cloud-debug-build-budget-report.json"
|
|
|
|
- name: Upload Android cloud-debug build budget report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: android-cloud-debug-build-budget
|
|
path: |
|
|
${{ runner.temp }}/android-cloud-debug-build-timing.json
|
|
${{ runner.temp }}/android-cloud-debug-build-budget-report.json
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
|
|
- name: Verify APK exists
|
|
run: |
|
|
APK_PATH="packages/app-core/platforms/android/app/build/outputs/apk/debug/app-debug.apk"
|
|
if [ ! -f "$APK_PATH" ]; then
|
|
echo "::error::Debug APK not found at $APK_PATH"
|
|
exit 1
|
|
fi
|
|
APK_SIZE=$(stat -c%s "$APK_PATH" 2>/dev/null || stat -f%z "$APK_PATH")
|
|
echo "Debug APK built: $APK_PATH ($APK_SIZE bytes)"
|
|
|
|
- name: Upload debug APK
|
|
if: ${{ hashFiles('packages/app-core/platforms/android/app/build/outputs/apk/debug/app-debug.apk') != '' }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: android-debug-apk
|
|
path: packages/app-core/platforms/android/app/build/outputs/apk/debug/app-debug.apk
|
|
retention-days: 7
|
|
if-no-files-found: error
|