Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
90b32db
feat(desktop): Milestone 6 — Tauri desktop shell over the headless en…
inhaq Jun 13, 2026
b14b831
feat(desktop): generate proper app icon set
inhaq Jun 13, 2026
01a64b3
fix(desktop): address PR review feedback
inhaq Jun 13, 2026
698d771
fix(desktop): close local-RCE vector, fix Rust build, wire sidecar, p…
inhaq Jun 13, 2026
96e0119
fix(server): harden token exposure, host binding, memory + admission …
inhaq Jun 13, 2026
5935378
fix: stale-event replay on session reuse, integration status, store +…
inhaq Jun 13, 2026
110b4ee
fix: persist failed runs, always release worktrees, surface integrati…
inhaq Jun 13, 2026
00759dd
fix: orphan recovery, task-fault isolation, and run cancellation
inhaq Jun 13, 2026
ce2db46
Merge branch 'main' into m6-push
inhaq Jun 13, 2026
be3e067
fix: thread cancellation into model runners and integration
inhaq Jun 13, 2026
9d50d82
ci: build and bundle the desktop/Tauri release path
inhaq Jun 13, 2026
d244a61
ci: fix Linux AppImage bundling on GitHub runners
inhaq Jun 13, 2026
b2b193b
ci: bundle deb+rpm on Linux, skip flaky AppImage step
inhaq Jun 13, 2026
7b650b8
fix: graceful shutdown, session-id validation, pinned Bun
inhaq Jun 13, 2026
dbd5141
fix: drain active runs on shutdown, align Tauri kill, audit root, add…
inhaq Jun 13, 2026
a72b456
chore(desktop): release metadata, release pipeline, shutdown tests
inhaq Jun 13, 2026
bf1b007
test(desktop): relax cross-platform timing in listener-close test
inhaq Jun 13, 2026
88bea4c
ci: gate releases on quality checks + tag/version match; derive keych…
inhaq Jun 13, 2026
4958d1d
Address review: serialize engine lifecycle; harden release workflow
inhaq Jun 13, 2026
ee10a49
Address review: least-privilege release workflow; guard engine restart
inhaq Jun 13, 2026
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
121 changes: 121 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,124 @@ jobs:

- name: Test
run: npm test

# Surface known advisories in the root dependency tree (the engine that
# ships in the sidecar). Run once on Linux since it is OS-independent.
- name: Audit dependencies
if: matrix.os == 'ubuntu-latest'
run: npm audit --audit-level=moderate

# Proves the desktop/Tauri release path actually builds: the Bun-compiled
# engine sidecar, the Vite frontend, the Rust crate (cargo), and the full
# `tauri build` bundle. The JS `build` job above only exercises the headless
# engine, so a break in the desktop shell would otherwise ship unnoticed.
desktop:
name: desktop (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22

# The sidecar (src/server/index.ts) is compiled to a standalone binary
# with Bun, which Tauri then embeds as an external binary. Pin the version
# so the shipped sidecar is built reproducibly rather than with whatever
# "latest" happens to be at build time.
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.14

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable

# Cache the (large) Cargo build of the Tauri dependency tree, keyed on the
# crate's Cargo.lock, so reruns don't recompile from scratch.
- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> target

# Tauri v2 needs the GTK/WebKit stack on Linux; the keyring crate's
# secret-service backend additionally needs libsecret. macOS and Windows
# ship the required system frameworks already.
- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libssl-dev \
libsecret-1-dev \
patchelf \
file \
build-essential

# The sidecar bundles src/, which imports from the root package's deps.
- name: Install root dependencies
run: npm ci

- name: Install desktop dependencies
run: npm ci
working-directory: desktop

# Surface known-high/critical advisories in the desktop dependency tree.
- name: Audit desktop dependencies
run: npm audit --audit-level=high
working-directory: desktop

# Build the engine sidecar binary (binaries/loopwright-engine-<triple>)
# before the Rust build, which validates the externalBin in build.rs.
- name: Build engine sidecar
run: npm run build:sidecar
working-directory: desktop

# tsc --noEmit && vite build -> produces desktop/dist for the Tauri build.
- name: Build desktop frontend
run: npm run build
working-directory: desktop

# Compiles the Rust crate (and runs build.rs / tauri-build) to prove the
# native side builds; there are no Rust unit tests yet.
- name: Cargo test
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --locked

# Full release-path build. On Linux we bundle deb + rpm but skip AppImage:
# its bundler (linuxdeploy, itself an AppImage) is unreliable on GitHub's
# runners due to the FUSE/sandbox restrictions, and deb + rpm already
# prove the compile-and-bundle pipeline. macOS/Windows bundle all targets.
- name: Tauri build (Linux, deb + rpm)
if: matrix.os == 'ubuntu-latest'
run: npm run tauri build -- --bundles deb,rpm
working-directory: desktop

- name: Tauri build (macOS/Windows)
if: matrix.os != 'ubuntu-latest'
run: npm run tauri build
working-directory: desktop

# Preserve the built installers so QA and users have a downloadable
# deliverable from every CI run. The release workflow publishes tagged
# builds to a GitHub Release; this keeps untagged builds available too.
- name: Upload installers
uses: actions/upload-artifact@v4
with:
name: loopwright-installers-${{ matrix.os }}
path: |
desktop/src-tauri/target/release/bundle/**
if-no-files-found: error
retention-days: 14
263 changes: 263 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
name: Release

# Publishes desktop installers to a GitHub Release. Triggered by pushing a
# version tag (e.g. v0.1.0); `workflow_dispatch` allows a manual draft build.
#
# A `gate` job runs the full quality suite (typecheck, tests, audits, cargo
# test) and, for tag builds, verifies the tag matches every manifest version
# BEFORE anything is published — so a bad tagged commit cannot produce a
# release. Code signing / notarization and updater signing are wired through
# the publish job's env block and activate automatically once the corresponding
# repository secrets are configured; until then the workflow still uploads
# unsigned installers as a draft release for QA. See the notes at the bottom.
on:
push:
tags: ["v*"]
workflow_dispatch:

# Least privilege: default the whole workflow to read-only so a compromised
# dependency install script or test step in the gate cannot mutate the repo.
# Only the publish job is granted contents: write (to create the Release).
permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
# Quality gate: must pass before any artifact is built or published. Runs on
# every release platform so a direct tag push exercises the JS + Rust test
# suites on macOS/Windows too, not just Linux. One-time, platform-independent
# checks (tag/version match, dependency audits) run on Linux only.
gate:
name: gate (${{ matrix.os }})
# Read-only: this job runs npm ci / tests and must never need write scope.
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Don't leave the GITHUB_TOKEN in .git/config where the subsequent
# `npm ci` / test steps (and their dependency scripts) could read it.
persist-credentials: false

- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22

# Fail fast (before the expensive toolchain/build steps) if a tag does not
# match the versions committed to every manifest. Platform-independent, so
# run it once on Linux.
- name: Verify tag matches manifest versions
if: matrix.os == 'ubuntu-latest' && github.ref_type == 'tag'
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
if ! printf '%s' "$tag" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::tag '${GITHUB_REF_NAME}' is not a vX.Y.Z release tag"
exit 1
fi
root=$(node -p "require('./package.json').version")
desk=$(node -p "require('./desktop/package.json').version")
conf=$(node -p "require('./desktop/src-tauri/tauri.conf.json').version")
cargo=$(grep -m1 -E '^version *= *"' desktop/src-tauri/Cargo.toml | sed -E 's/^version *= *"([^"]+)".*/\1/')
fail=0
for pair in "root package.json:${root}" "desktop/package.json:${desk}" "tauri.conf.json:${conf}" "Cargo.toml:${cargo}"; do
name="${pair%%:*}"; ver="${pair##*:}"
if [ "${ver}" != "${tag}" ]; then
echo "::error::${name} version '${ver}' does not match tag '${tag}'"
fail=1
fi
done
if [ "${fail}" -ne 0 ]; then
echo "Bump every manifest to ${tag} before tagging the release."
exit 1
fi
echo "All manifests match ${tag}."

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.14

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> target

- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libssl-dev \
libsecret-1-dev \
patchelf \
file \
build-essential

- name: Install root dependencies
run: npm ci

- name: Install desktop dependencies
run: npm ci
working-directory: desktop

- name: Type check
run: npm run typecheck

- name: Test
run: npm test

# Dependency audits are platform-independent; run them once on Linux.
- name: Audit root dependencies
if: matrix.os == 'ubuntu-latest'
run: npm audit --audit-level=moderate

- name: Audit desktop dependencies
if: matrix.os == 'ubuntu-latest'
run: npm audit --audit-level=high
working-directory: desktop

- name: Build engine sidecar
run: npm run build:sidecar
working-directory: desktop

- name: Build desktop frontend
run: npm run build
working-directory: desktop

- name: Cargo test
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --locked

release:
name: release (${{ matrix.os }})
needs: gate
# The only job that needs write scope — it creates/uploads the GitHub
# Release. tauri-action authenticates via the GITHUB_TOKEN env var below,
# not persisted git credentials, so the checkout stays credential-free.
permissions:
contents: write
runs-on: ${{ matrix.os }}
strategy:
# Build every platform even if one fails, so a single platform issue does
# not block publishing the others.
fail-fast: false
matrix:
include:
# Linux ships deb + rpm (AppImage's linuxdeploy is unreliable on
# GitHub runners; see build.yml).
- os: ubuntu-latest
args: "--bundles deb,rpm"
- os: macos-latest
args: ""
- os: windows-latest
args: ""

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.14

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> target

- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libssl-dev \
libsecret-1-dev \
patchelf \
file \
build-essential

# The sidecar bundles src/, which imports from the root package's deps;
# tauri-action runs the beforeBuildCommand (build:sidecar && build) which
# needs both dependency trees installed.
- name: Install root dependencies
run: npm ci

- name: Install desktop dependencies
run: npm ci
working-directory: desktop

- name: Build, bundle, and publish the desktop app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# --- macOS signing + notarization (used automatically when set) ---
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# --- Updater artifact signing (used automatically when set) ---
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: desktop
# On a tag push, reuse the tag; on manual dispatch, derive a unique tag.
tagName: ${{ github.ref_type == 'tag' && github.ref_name || format('v0.0.0-dev.{0}', github.run_number) }}
releaseName: Loopwright ${{ github.ref_type == 'tag' && github.ref_name || format('dev build {0}', github.run_number) }}
# Publish as a draft so a human reviews artifacts before release.
releaseDraft: true
# Manual (workflow_dispatch) builds produce a throwaway dev tag, so
# mark them prerelease — otherwise publishing the draft would let
# GitHub treat a dev build as a normal release. Real version tags stay
# non-prerelease.
prerelease: ${{ github.ref_type != 'tag' }}
args: ${{ matrix.args }}

# Production distribution checklist (requires team-owned credentials/decisions):
# - macOS: add APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD /
# APPLE_SIGNING_IDENTITY / APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID secrets
# to sign + notarize. Windows: configure a code-signing cert in tauri.conf
# (bundle.windows.certificateThumbprint or a signCommand) + secret.
# - Confirm the bundle identifier (currently dev.loopwright.desktop) maps to a
# domain the org controls before first public release. The keychain service
# namespace follows the identifier automatically (see secrets.rs).
# - Auto-update: add the tauri updater plugin + endpoints and a signing key
# (TAURI_SIGNING_PRIVATE_KEY) to ship update artifacts.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ dist/
*.log
.DS_Store
sessions.db

# Desktop app (Tauri) build artifacts
desktop/src-tauri/binaries/
desktop/src-tauri/target/
desktop/src-tauri/gen/
desktop/dist/
desktop/node_modules/
Loading
Loading