mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 17:34:51 +00:00
chore: merge origin/main into timeline integration
This commit is contained in:
@@ -104,3 +104,27 @@ jobs:
|
||||
|
||||
- name: Build Nix desktop package
|
||||
run: nix build .#desktop -o result-desktop
|
||||
|
||||
build-desktop-darwin:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Build Nix desktop package
|
||||
run: nix build .#desktop -o result-desktop
|
||||
|
||||
- name: Verify macOS application bundle
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
app="result-desktop/Applications/Paseo.app"
|
||||
test -x "$app/Contents/MacOS/Paseo"
|
||||
test -L result-desktop/bin/paseo-desktop
|
||||
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$app/Contents/Info.plist")" = "sh.paseo.desktop"
|
||||
find "$app" -name Info.plist -print0 | xargs -0 -n 1 plutil -lint
|
||||
|
||||
@@ -25,6 +25,19 @@ desktop launches always force the daemon to production mode.
|
||||
|
||||
`npm run dev` is only a shorthand for `npm run dev:server`. Keep `127.0.0.1:6767` for the packaged app and production-style `~/.paseo` state.
|
||||
|
||||
## Nix desktop package
|
||||
|
||||
The flake exposes `packages.<system>.desktop` on Linux and macOS:
|
||||
|
||||
```bash
|
||||
nix build .#desktop
|
||||
```
|
||||
|
||||
Linux produces the `paseo-desktop` launcher and desktop entry. macOS produces
|
||||
`Applications/Paseo.app` plus the `paseo-desktop` launcher. Both use the nixpkgs
|
||||
Electron runtime and the checkout's built daemon, client, and renderer rather
|
||||
than downloading a published desktop release.
|
||||
|
||||
### PASEO_HOME
|
||||
|
||||
`PASEO_HOME` is the directory that holds runtime state (agents, worktrees, workspace config, sockets, daemon log). Resolution rules:
|
||||
|
||||
@@ -26,16 +26,10 @@
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
paseo = pkgs.callPackage ./nix/package.nix { };
|
||||
isLinux = nixpkgs.lib.elem system [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
in
|
||||
{
|
||||
default = paseo;
|
||||
paseo = paseo;
|
||||
}
|
||||
// nixpkgs.lib.optionalAttrs isLinux {
|
||||
desktop = pkgs.callPackage ./nix/desktop-package.nix { inherit paseo; };
|
||||
}
|
||||
);
|
||||
|
||||
+112
-55
@@ -5,6 +5,7 @@
|
||||
nodejs_22,
|
||||
python3,
|
||||
makeWrapper,
|
||||
autoPatchelfHook,
|
||||
copyDesktopItems,
|
||||
makeDesktopItem,
|
||||
electron,
|
||||
@@ -15,19 +16,16 @@
|
||||
# the upstream hash via `paseo.override { npmDepsHash = "..."; }`.
|
||||
paseo,
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
buildNpmPackage {
|
||||
pname = "paseo-desktop";
|
||||
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
|
||||
|
||||
src = lib.cleanSourceWith {
|
||||
src = ./..;
|
||||
filter =
|
||||
path: type:
|
||||
let
|
||||
baseName = builtins.baseNameOf path;
|
||||
relPath = lib.removePrefix (toString ./..) path;
|
||||
in
|
||||
filter = path: type: let
|
||||
baseName = builtins.baseNameOf path;
|
||||
relPath = lib.removePrefix (toString ./..) path;
|
||||
in
|
||||
# Exclude mobile-only platform code (we only need the web/electron build)
|
||||
!(lib.hasPrefix "/packages/app/android" relPath)
|
||||
&& !(lib.hasPrefix "/packages/app/ios" relPath)
|
||||
@@ -63,16 +61,23 @@ buildNpmPackage rec {
|
||||
|
||||
# Prevent onnxruntime-node's install script from running during automatic
|
||||
# npm rebuild. We manually rebuild only node-pty in buildPhase.
|
||||
npmRebuildFlags = [ "--ignore-scripts" ];
|
||||
npmRebuildFlags = ["--ignore-scripts"];
|
||||
|
||||
nativeBuildInputs = [
|
||||
python3 # for node-gyp (node-pty)
|
||||
makeWrapper
|
||||
copyDesktopItems
|
||||
nativeBuildInputs =
|
||||
[
|
||||
python3 # for node-gyp (node-pty)
|
||||
]
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||
autoPatchelfHook
|
||||
makeWrapper
|
||||
copyDesktopItems
|
||||
];
|
||||
|
||||
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
|
||||
libuv
|
||||
stdenv.cc.cc.lib # libstdc++ for sherpa-onnx prebuilt binaries
|
||||
];
|
||||
|
||||
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ libuv ];
|
||||
|
||||
dontNpmBuild = true;
|
||||
|
||||
env = {
|
||||
@@ -97,61 +102,113 @@ buildNpmPackage rec {
|
||||
# Expo web export for the Electron renderer
|
||||
( cd packages/app && PASEO_WEB_PLATFORM=electron npx expo export --platform web )
|
||||
|
||||
# Desktop main process (tsc only — NOT electron-builder)
|
||||
# Desktop main process
|
||||
npm run build:main --workspace=@getpaseo/desktop
|
||||
|
||||
${lib.optionalString stdenv.hostPlatform.isDarwin ''
|
||||
# Let electron-builder create the native bundle layout (including helper
|
||||
# app names and bundle identifiers), but source Electron from nixpkgs
|
||||
# instead of downloading a release at build time. electron-builder edits
|
||||
# the copied helper plists, so stage a writable distribution rather than
|
||||
# pointing it directly at the read-only Nix store.
|
||||
electron_dist="$NIX_BUILD_TOP/electron-dist"
|
||||
mkdir -p "$electron_dist"
|
||||
cp -R ${electron}/Applications/Electron.app "$electron_dist/"
|
||||
chmod -R u+w "$electron_dist/Electron.app"
|
||||
(
|
||||
cd packages/desktop
|
||||
# The Nix output is not a distributable DMG, so leave it unsigned and
|
||||
# disable the hardened runtime that requires a matching signature.
|
||||
CSC_IDENTITY_AUTO_DISCOVERY=false \
|
||||
../../node_modules/.bin/electron-builder \
|
||||
--config electron-builder.yml \
|
||||
--dir \
|
||||
--mac \
|
||||
--publish never \
|
||||
--config.electronDist="$electron_dist" \
|
||||
--config.mac.identity=null \
|
||||
--config.mac.hardenedRuntime=false \
|
||||
--config.mac.notarize=false
|
||||
)
|
||||
''}
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/share/paseo-desktop $out/bin
|
||||
mkdir -p $out/bin
|
||||
|
||||
# Preserve the monorepo layout so main.js's dev-mode path resolution
|
||||
# (`__dirname/../../app/dist`, `__dirname/../assets/icon.png`) works
|
||||
# without patching: invoked unpackaged via `electron path/to/main.js`,
|
||||
# `app.isPackaged` is false, so these relative paths are used.
|
||||
#
|
||||
# Copy the entire packages/ tree (not just built artifacts) because npm
|
||||
# creates workspace symlinks from node_modules/@getpaseo/* into packages/*.
|
||||
# Missing any workspace package leaves dangling symlinks and fails the
|
||||
# noBrokenSymlinks output check. The cleanSourceWith filter above already
|
||||
# drops the big platform-specific things (android/ios, website, tests).
|
||||
cp package.json $out/share/paseo-desktop/
|
||||
cp -a packages $out/share/paseo-desktop/
|
||||
cp -a node_modules $out/share/paseo-desktop/
|
||||
${lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||
mkdir -p $out/share/paseo-desktop
|
||||
|
||||
# Skills directory referenced at runtime by some agents
|
||||
if [ -d skills ]; then
|
||||
cp -a skills $out/share/paseo-desktop/
|
||||
fi
|
||||
# Materialize only the desktop and daemon runtime graphs. Copying the
|
||||
# complete monorepo used to ship every build-time dependency (including
|
||||
# Electron, Expo tooling, and cross-platform builder binaries), making the
|
||||
# desktop output larger than 2 GiB.
|
||||
PASEO_TRACE_DESKTOP=1 node scripts/trace-daemon.mjs > desktop-files.txt
|
||||
|
||||
# Hicolor icon for desktop environments
|
||||
install -Dm644 packages/desktop/assets/icon.png \
|
||||
$out/share/icons/hicolor/512x512/apps/paseo-desktop.png
|
||||
while IFS= read -r path; do
|
||||
[ -z "$path" ] && continue
|
||||
mkdir -p "$out/share/paseo-desktop/$(dirname "$path")"
|
||||
cp -a "$path" "$out/share/paseo-desktop/$path"
|
||||
done < desktop-files.txt
|
||||
|
||||
# Launcher wraps nixpkgs electron.
|
||||
# --no-sandbox: Chromium's setuid sandbox can't live in /nix/store
|
||||
# (immutable, no setuid). Acceptable for v1; a follow-up can wire
|
||||
# `security.wrappers` via a NixOS module for users who want the sandbox.
|
||||
#
|
||||
# EXPO_DEV_URL: We run unpackaged via `electron path/to/main.js`, so
|
||||
# `app.isPackaged` is false. In that mode main.ts loads `DEV_SERVER_URL`
|
||||
# (defaults to http://localhost:8081 — the Expo dev server, which doesn't
|
||||
# exist here). Point it at the `paseo://` protocol handler instead, which
|
||||
# serves from `__dirname/../../app/dist` (our install layout matches).
|
||||
makeWrapper ${electron}/bin/electron $out/bin/paseo-desktop \
|
||||
--add-flags "$out/share/paseo-desktop/packages/desktop/dist/main.js" \
|
||||
--add-flags "--no-sandbox" \
|
||||
--set EXPO_DEV_URL "paseo://app/"
|
||||
# Keep the same unpackaged monorepo layout expected by main.js.
|
||||
cp package.json $out/share/paseo-desktop/
|
||||
mkdir -p $out/share/paseo-desktop/packages/app
|
||||
cp -a packages/app/dist $out/share/paseo-desktop/packages/app/
|
||||
|
||||
copyDesktopItems
|
||||
for runtime_path in \
|
||||
packages/desktop/dist/main.js \
|
||||
packages/desktop/dist/preload.js \
|
||||
packages/desktop/dist/features/browser-keyboard/guest-preload.js \
|
||||
packages/desktop/package.json; do
|
||||
if [ ! -e "$out/share/paseo-desktop/$runtime_path" ]; then
|
||||
echo "desktop runtime trace omitted $runtime_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -e $out/share/paseo-desktop/node_modules/electron ]; then
|
||||
echo "desktop runtime trace included npm Electron" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Skills directory referenced at runtime by some agents
|
||||
if [ -d skills ]; then
|
||||
cp -a skills $out/share/paseo-desktop/
|
||||
fi
|
||||
|
||||
# Hicolor icon for desktop environments
|
||||
install -Dm644 packages/desktop/assets/icon.png \
|
||||
$out/share/icons/hicolor/512x512/apps/paseo-desktop.png
|
||||
|
||||
# Chromium's setuid sandbox cannot live in the immutable Nix store.
|
||||
makeWrapper ${electron}/bin/electron $out/bin/paseo-desktop \
|
||||
--add-flags "$out/share/paseo-desktop/packages/desktop/dist/main.js" \
|
||||
--add-flags "--no-sandbox" \
|
||||
--set EXPO_DEV_URL "paseo://app/"
|
||||
|
||||
copyDesktopItems
|
||||
''}
|
||||
|
||||
${lib.optionalString stdenv.hostPlatform.isDarwin ''
|
||||
app="$(find packages/desktop/release -maxdepth 3 -type d -name Paseo.app -print -quit)"
|
||||
if [ -z "$app" ]; then
|
||||
echo "electron-builder did not produce Paseo.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$out/Applications"
|
||||
cp -R "$app" "$out/Applications/Paseo.app"
|
||||
ln -s ../Applications/Paseo.app/Contents/MacOS/Paseo "$out/bin/paseo-desktop"
|
||||
''}
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
desktopItems = [
|
||||
desktopItems = lib.optionals stdenv.hostPlatform.isLinux [
|
||||
(makeDesktopItem {
|
||||
name = "paseo-desktop";
|
||||
desktopName = "Paseo";
|
||||
@@ -159,7 +216,7 @@ buildNpmPackage rec {
|
||||
comment = "Self-hosted daemon for AI coding agents";
|
||||
exec = "paseo-desktop";
|
||||
icon = "paseo-desktop";
|
||||
categories = [ "Development" ];
|
||||
categories = ["Development"];
|
||||
startupWMClass = "Paseo";
|
||||
})
|
||||
];
|
||||
@@ -169,6 +226,6 @@ buildNpmPackage rec {
|
||||
homepage = "https://github.com/getpaseo/paseo";
|
||||
license = lib.licenses.agpl3Plus;
|
||||
mainProgram = "paseo-desktop";
|
||||
platforms = lib.platforms.linux;
|
||||
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,14 +28,24 @@ const { sherpaPlatformPackageName } = await import(
|
||||
).href
|
||||
);
|
||||
|
||||
const traceDesktop = process.env.PASEO_TRACE_DESKTOP === "1";
|
||||
|
||||
// Daemon entry points. Workers forked into their own Node processes have
|
||||
// independent require trees; nft does not follow fork boundaries, so trace
|
||||
// them separately.
|
||||
// them separately. The desktop derivation opts into tracing its Electron main
|
||||
// process and preloads as well.
|
||||
const entries = [
|
||||
"packages/cli/dist/index.js",
|
||||
"packages/server/dist/scripts/supervisor-entrypoint.js",
|
||||
"packages/server/dist/server/terminal/terminal-worker-process.js",
|
||||
"packages/server/dist/server/server/speech/providers/local/worker-process.js",
|
||||
...(traceDesktop
|
||||
? [
|
||||
"packages/desktop/dist/main.js",
|
||||
"packages/desktop/dist/preload.js",
|
||||
"packages/desktop/dist/features/browser-keyboard/guest-preload.js",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// Files read at runtime via fs APIs rather than `require`. nft only
|
||||
@@ -59,6 +69,16 @@ const additionalInputs = [
|
||||
// Copy the wrapper plus the host platform package explicitly.
|
||||
"node_modules/sherpa-onnx-node/**",
|
||||
`node_modules/${sherpaPlatformPackageName()}/**`,
|
||||
...(traceDesktop
|
||||
? [
|
||||
// The unpackaged Nix launcher resolves these beside desktop/dist.
|
||||
"packages/desktop/package.json",
|
||||
"packages/desktop/assets/**",
|
||||
// resolveExternalCliEntrypoint() looks up the workspace through this
|
||||
// link at runtime; nft traces the target files but not the link.
|
||||
"node_modules/@getpaseo/cli",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// Trace.
|
||||
@@ -83,6 +103,9 @@ const { fileList, warnings } = await nodeFileTrace(entries, {
|
||||
// tries to walk into them via index files. Belt and suspenders.
|
||||
"**/*.test.js",
|
||||
"**/*.e2e.test.js",
|
||||
// The Nix desktop package runs under nixpkgs' Electron. Tracing the npm
|
||||
// package would duplicate the complete Electron distribution in $out.
|
||||
...(traceDesktop ? ["node_modules/electron/**"] : []),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user