Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ env:
PILOTDECK_UPDATE_REPOSITORY: ${{ github.repository }}

jobs:
linux:
uses: ./.github/workflows/desktop-linux.yml
with:
source_sha: ${{ inputs.source_sha || github.sha }}
release_date: ${{ inputs.release_date }}
revision: ${{ inputs.revision || '0' }}

macos:
name: macOS ${{ matrix.arch }}
strategy:
Expand Down
147 changes: 147 additions & 0 deletions .github/workflows/desktop-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
name: Ubuntu DEB Smoke

on:
workflow_call:
inputs:
source_sha:
description: Exact source commit to build
required: true
type: string
release_date:
description: Optional release date in YYYY-MM-DD
required: false
default: ""
type: string
revision:
description: Zero-based same-day build revision
required: false
default: "0"
type: string

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
PILOTDECK_UPDATE_REPOSITORY: ${{ github.repository }}

jobs:
build-and-smoke:
name: Ubuntu 22.04 ${{ matrix.arch }} build and smoke
strategy:
fail-fast: false
matrix:
include:
- arch: x64
deb_arch: amd64
unpacked_dir: linux-unpacked
runner: ubuntu-22.04
- arch: arm64
deb_arch: arm64
unpacked_dir: linux-arm64-unpacked
runner: ubuntu-22.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
env:
PILOTDECK_RELEASE_DATE: ${{ inputs.release_date }}
PILOTDECK_RELEASE_REVISION: ${{ inputs.revision }}
PILOTDECK_COMMIT_SHA: ${{ inputs.source_sha || github.sha }}
PILOTDECK_DESKTOP_NODE_ARCH: ${{ matrix.arch }}
NODE_OPTIONS: --max-old-space-size=2560
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.source_sha || github.sha }}

- uses: pnpm/action-setup@v6
with:
version: 10.32.1

- uses: actions/setup-node@v7
with:
node-version: 22.23.1
cache: pnpm

- name: Verify native Ubuntu 22.04 build host
run: node apps/desktop/scripts/check-linux-build-host.mjs ${{ matrix.arch }}

- name: Install workspace dependencies
run: pnpm install --frozen-lockfile

- name: Verify Linux background and quit lifecycle
run: node --test apps/desktop/scripts/desktop-lifecycle.test.mjs

- name: Set desktop release metadata
run: node apps/desktop/scripts/set-ci-version.mjs

- name: Build Ubuntu ${{ matrix.arch }} DEB
run: pnpm --filter pilotdeck-desktop run dist:linux:${{ matrix.arch }}

- name: Verify Ubuntu artifact and updater
shell: bash
env:
EXPECTED_DEB_ARCH: ${{ matrix.deb_arch }}
EXPECTED_ARCH: ${{ matrix.arch }}
UNPACKED_DIR: ${{ matrix.unpacked_dir }}
run: |
set -euo pipefail
deb_path="$(find apps/desktop/dist-electron -maxdepth 1 -name "*-linux-${EXPECTED_DEB_ARCH}.deb" -print -quit)"
test -n "$deb_path"
test "$(dpkg-deb --field "$deb_path" Architecture)" = "$EXPECTED_DEB_ARCH"
feed="latest-linux.yml"
if [ "$EXPECTED_ARCH" = arm64 ]; then feed="latest-linux-arm64.yml"; fi
test -s "apps/desktop/dist-electron/$feed"
grep -Fq "$(basename "$deb_path")" "apps/desktop/dist-electron/$feed"
app_dir="apps/desktop/dist-electron/$UNPACKED_DIR"
test -f "$app_dir/resources/package-type"
test "$(cat "$app_dir/resources/package-type")" = deb
ELECTRON_RUN_AS_NODE=1 "$app_dir/pilotdeck-desktop" \
apps/desktop/scripts/verify-updater.cjs "$app_dir/resources/app.asar"
echo "DEB_PATH=$deb_path" >> "$GITHUB_ENV"

- name: Install headless display and launcher validation tools
run: |
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
dbus-x11 desktop-file-utils weston x11-utils xauth xvfb

- name: Install DEB and exercise bundled native modules
env:
EXPECTED_ARCH: ${{ matrix.arch }}
run: |
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "./$DEB_PATH"
desktop-file-validate /usr/share/applications/pilotdeck-desktop.desktop
/opt/PilotDeck/resources/node/bin/node \
apps/desktop/scripts/verify-linux-package.cjs "$EXPECTED_ARCH"

- name: Smoke installed app under X11
run: bash apps/desktop/scripts/run-linux-display-smoke.sh x11

- name: Smoke installed app under Wayland
run: bash apps/desktop/scripts/run-linux-display-smoke.sh wayland

- name: Verify Linux menus, language and theme under X11
run: bash apps/desktop/scripts/run-linux-chrome-smoke.sh x11

- name: Verify Linux menus, language and theme under Wayland
run: bash apps/desktop/scripts/run-linux-chrome-smoke.sh wayland

- name: Upload Linux chrome screenshots
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: pilotdeck-linux-chrome-${{ matrix.arch }}
path: outputs/desktop-chrome-review/linux-*/linux-*.png
if-no-files-found: ignore
retention-days: 14

- name: Upload Ubuntu installer
uses: actions/upload-artifact@v7
with:
name: pilotdeck-desktop-ubuntu-${{ matrix.arch }}
overwrite: true
path: |
apps/desktop/dist-electron/*-linux-${{ matrix.deb_arch }}.deb
apps/desktop/dist-electron/latest-linux*.yml
if-no-files-found: error
retention-days: 14
8 changes: 7 additions & 1 deletion .github/workflows/desktop-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ jobs:

- name: Test desktop updater network
# Requires electron-updater, intentionally absent from the Web-only job.
run: pnpm --dir ui exec vitest run server/services/desktopUpdateNetwork.test.ts
run: pnpm --dir ui exec vitest run server/services/desktopUpdateNetwork.test.ts server/services/desktopUpdates.spec.ts

# Use the same native Ubuntu package and display checks before merge and release.
ubuntu-deb:
uses: ./.github/workflows/desktop-linux.yml
with:
source_sha: ${{ github.sha }}

# Run the same installer build and artifact/feed validation as Daily Release.
# A process-only check cannot catch NSIS compilation or packaging regressions.
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,13 @@ jobs:
find release-assets -maxdepth 1 -name '*-setup.exe' -print -quit | grep -q .
find release-assets -maxdepth 1 -name '*-mac-arm64.zip' -print -quit | grep -q .
find release-assets -maxdepth 1 -name '*-mac-x64.zip' -print -quit | grep -q .
find release-assets -maxdepth 1 -name '*-linux-arm64.deb' -print -quit | grep -q .
find release-assets -maxdepth 1 -name '*-linux-amd64.deb' -print -quit | grep -q .
test -s release-assets/latest-arm64-mac.yml
test -s release-assets/latest-x64-mac.yml
test -s release-assets/latest-x64.yml
test -s release-assets/latest-linux.yml
test -s release-assets/latest-linux-arm64.yml
test -s release-assets/release.json
test -s release-assets/SHA256SUMS.txt

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/web-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ jobs:
# and streamSmoother's fake-timer spec is independently flaky. Keep the
# rest of the Web suite as the merge gate until those baseline tests are
# repaired in their own change.
# Desktop Smoke runs the updater network suite with desktop dependencies.
# Desktop Smoke runs both updater suites with desktop dependencies.
run: >-
pnpm --dir ui exec vitest run
--exclude 'e2e/**'
--exclude 'src/components/chat/hooks/streamSmoother.test.ts'
--exclude 'server/services/desktopUpdateNetwork.test.ts'
--exclude 'server/services/desktopUpdates.spec.ts'

docker:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ vite.config.ts.timestamp-*

# Local recording / artifacts
artifacts/
outputs/desktop-chrome-review/
!src/session/artifacts/
!src/session/artifacts/**

Expand Down
71 changes: 56 additions & 15 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ The tray's **Quit** command and **File > Exit** restore the main window and show
an owned confirmation dialog. Cancel is the default. Confirming stops the managed
runtime before the application exits; automatic updates use their existing quit
path without a second confirmation. Tray menus and confirmation text follow the
application language. Linux retains its existing close behavior.
application language.

### Ubuntu

Closing the main window keeps the app and local tasks running when a system tray
is available. The PilotDeck icon in Ubuntu's top panel opens a native menu with
**Open main window** and **Quit**. Select **Open main window** to restore it.
Quit asks for confirmation and stops managed tasks. Launching PilotDeck again
restores the existing window instead of starting another runtime. If creating
the tray icon fails, closing the window quits normally so it is not stranded
in the background. The desktop environment must provide a status icon host,
such as Ubuntu's AppIndicators extension, to show the top-panel icon.

Run `node --test apps/desktop/scripts/desktop-lifecycle.test.mjs` from the repository
root for the cross-platform controller checks. On macOS or Windows,
Expand Down Expand Up @@ -77,13 +88,26 @@ Regenerate status icons from the checked-in SVG with
pnpm --filter pilotdeck-desktop dist:mac:arm64
pnpm --filter pilotdeck-desktop dist:mac:x64
pnpm --filter pilotdeck-desktop dist:win
# On a native Ubuntu host with the matching CPU architecture:
pnpm --filter pilotdeck-desktop dist:linux:arm64
pnpm --filter pilotdeck-desktop dist:linux:x64
```

Build Linux release packages on Ubuntu 22.04 LTS for the intended minimum
version. Native modules compiled on newer Ubuntu releases may require a newer
glibc and will not run on 22.04. A 4 GB build VM may need
`NODE_OPTIONS=--max-old-space-size=2560` for the Web UI build. Linux packages
use the system Git package and include a bundled Node.js runtime.
See [Ubuntu desktop support assessment](../../docs/ubuntu-desktop-support.md)
for the X11, Wayland, and XWayland test matrix and remaining release gates.

Platform release builds should run on matching GitHub Actions runners:

- macOS arm64 DMG artifacts on `macos-latest`
- macOS x64 DMG artifacts on `macos-15-intel`
- Windows x64 NSIS installer artifacts on `windows-latest`
- Ubuntu x64 DEB artifacts on `ubuntu-22.04`
- Ubuntu arm64 DEB artifacts on `ubuntu-22.04-arm`

macOS CI signs and notarizes release artifacts when the repository provides
these GitHub Secrets:
Expand Down Expand Up @@ -113,28 +137,35 @@ compatibility guarantees.
macOS keeps native traffic lights in the sidebar's 48px top strip. The main
header extends to the window edge. Collapsing the sidebar, entering compact
mode or opening Settings reserves a full-width strip; fullscreen removes it.
Windows uses a 40px title-bar overlay with native caption buttons. The caption
Windows and Ubuntu use a 40px title-bar overlay with native caption buttons,
putting the application menus and window controls on one row. The caption
has a neutral light/dark background; the main interface keeps its original sidebar
gradient, layout, rounded frame, borders and viewport gutter.
File, Edit, View, Go and Help open the corresponding native submenus; Alt+F/E/V/G/H
access them directly and F10 opens the complete application menu, including in
fullscreen. Tab and arrow keys navigate caption buttons. Menu clicks preserve
the editor selection. While a popup is open, hovering or clicking another caption
menu switches directly to it. The main process tracks the native cursor during
that session because native popups capture renderer mouse events, and waits for
the previous popup to close before opening its replacement.
File, Edit, View, Go and Help open the corresponding menu sections; Alt+F/E/V/G/H
access them directly. F10 opens the application menu on Windows and the File
section on Linux, including in fullscreen. Tab and arrow keys navigate caption
buttons. Menu clicks preserve the editor selection. While a popup is open,
hovering or clicking another caption menu switches directly to it. Windows uses
native submenus and tracks the native cursor while they are open. Linux renders
the popup from the same current application menu template inside the window;
the native menu remains registered for keyboard accelerators and OS integration.
Its drag region reserves the native controls' area through
the Window Controls Overlay geometry; the workspace header also supports dragging.
Alt does not reveal a duplicate system menu row. Windows fullscreen state is
published after the native transition updates, removing/restoring the top inset.
Both platforms retain native frames, resizing and shadows. macOS removes the Web
viewport's extra gutter, border, rounded corners and shadow. Windows only reserves
All three platforms retain native frames, resizing and shadows. macOS removes the Web
viewport's extra gutter, border, rounded corners and shadow. Windows and Ubuntu reserve
space for the caption; business components and browser/PWA styles are preserved.
The caption is preload-owned, so it also works during startup and onboarding.
Caption labels initialize from the saved desktop language and follow the UI's
resolved i18n language immediately, together with the native application menu.
The native theme follows the app's light/dark/system preference. Opaque surfaces
are intentional; this implementation does not require vibrancy or Mica.
The caption, Linux popup and Electron native theme follow the app's
light/dark/system preference immediately. The Linux popup does not depend on a
particular GTK theme, panel extension or desktop environment, so the same menu
code applies on Ubuntu, Kylin, Deepin and UOS when their Electron and package
dependencies are compatible. Distribution-level installation and tray behavior
still require testing on each target desktop. Opaque surfaces are intentional;
this implementation does not require vibrancy or Mica.

The native menu routes New Conversation, New Project, Settings, Find, sidebar
visibility, Conversation, Project Files, Skills, Scheduled Tasks and Check for
Expand Down Expand Up @@ -170,13 +201,23 @@ pnpm --dir ui exec vite --host 127.0.0.1 --port 5187 --strictPort
node ui/e2e/desktop-chrome.smoke.mjs
```

On Ubuntu 22.04, run the same smoke suite under both X11 and a headless Wayland
compositor with `bash apps/desktop/scripts/run-linux-chrome-smoke.sh x11` and
`bash apps/desktop/scripts/run-linux-chrome-smoke.sh wayland`. It verifies the
single-row caption, menu commands, Chinese/English labels in the caption and
Linux popup, and light/dark menu colors. The Linux CI job runs both display
paths on x64 and arm64 before PR merge and in the daily release pipeline.

The smoke host uses the real compiled preload and application UI with a temporary
Electron profile and mocked API/WebSocket traffic. It does not start the real
runtime or send model requests. It invokes native menu callbacks to check commands,
project inheritance, dialog blocking, sidebar collapse, repeated update checks,
light/dark appearance, native fullscreen transitions and traffic-light position,
and minimize/restore. Windows also checks native popup requests, caption/sidebar
colors, control safe area, maximize/restore and fullscreen inset restoration.
and minimize/restore. Windows checks native popup requests; Linux checks its
interactive popup contents and live theme/language updates. Both check
caption/sidebar colors, control safe area and fullscreen inset restoration.
Windows additionally checks maximize/restore; the headless Linux compositors
have no window manager to verify maximize behavior.
Set `PILOTDECK_CHROME_SCALE` to `1`, `1.25` or `1.5` for Windows scaling checks.
Screenshots go to `outputs/desktop-chrome-review` (override
with `PILOTDECK_CHROME_ARTIFACTS`). Run the lifecycle suite above as well when
Expand Down
28 changes: 28 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"private": true,
"description": "Cross-platform Electron shell for PilotDeck.",
"author": "PilotDeck",
"homepage": "https://pilotdeck.openbmb.cn",
"license": "AGPL-3.0-only",
"desktopName": "pilotdeck-desktop",
"main": "dist/main.js",
"scripts": {
"build:runtime": "node scripts/build-runtime.mjs",
Expand All @@ -15,6 +18,8 @@
"dist:mac:x64": "PILOTDECK_DESKTOP_NODE_ARCH=x64 node scripts/build-runtime.mjs && tsc -p tsconfig.json && electron-builder --mac --x64 --publish never",
"dist:win": "node scripts/build-runtime.mjs && tsc -p tsconfig.json && electron-builder --win --publish never",
"dist:win:portable": "node scripts/build-runtime.mjs && tsc -p tsconfig.json && electron-builder --win portable --publish never",
"dist:linux:arm64": "node scripts/check-linux-build-host.mjs arm64 && umask 022 && PILOTDECK_DESKTOP_NODE_ARCH=arm64 node scripts/build-runtime.mjs && tsc -p tsconfig.json && electron-builder --linux deb --arm64 --publish never",
"dist:linux:x64": "node scripts/check-linux-build-host.mjs x64 && umask 022 && PILOTDECK_DESKTOP_NODE_ARCH=x64 node scripts/build-runtime.mjs && tsc -p tsconfig.json && electron-builder --linux deb --x64 --publish never",
"download-git-bash": "node scripts/download-git-bash.mjs",
"download-node": "node scripts/download-node.mjs",
"download-playwright-browsers": "node scripts/download-playwright-browsers.mjs",
Expand Down Expand Up @@ -106,6 +111,29 @@
}
]
},
"linux": {
"category": "Development",
"icon": "resources/icons/icon.png",
"maintainer": "OpenBMB <contact@openbmb.cn>",
"syncDesktopName": true,
"target": ["deb"]
},
"deb": {
"depends": [
"git",
"libgtk-3-0",
"libasound2",
"libgbm1",
"libnotify4",
"libnss3",
"libxss1",
"libxtst6",
"xdg-utils",
"libatspi2.0-0",
"libuuid1",
"libsecret-1-0"
]
},
"nsis": {
"artifactName": "${productName}-${version}-${os}-${arch}-setup.${ext}",
"oneClick": false,
Expand Down
Loading
Loading