Merge remote-tracking branch 'origin/main' into dev

This commit is contained in:
Mohamed Boudra
2026-04-10 19:41:19 +07:00
15 changed files with 90 additions and 60 deletions
+17
View File
@@ -1,5 +1,22 @@
# Changelog # Changelog
## 0.1.52 - 2026-04-10
### Added
- Theme selector — choose from six themes including Midnight, Claude, and Ghostty dark variants.
- Branch switching — switch git branches directly from the workspace header, with automatic stash and restore for uncommitted changes.
- Auto-download updates — desktop updates download silently in the background so they're ready to install when you are.
### Fixed
- Layout now responds correctly when resizing the window or rotating a tablet — previously the app could get stuck in mobile layout on a large screen.
- Terminal no longer causes massive memory spikes from snapshot thrashing during heavy output.
- Typing in the terminal works reliably — special keys, Ctrl combos, and paste are handled natively by the terminal emulator.
- Initializing agents no longer show a loading spinner as if they're running.
- Reconnecting to a running agent now works even when session persistence is unavailable.
- Error screens on desktop are now scrollable.
- Model list refreshes in the background when you open the model selector.
- Draft agent feature preferences (like thinking mode) are remembered across sessions.
## 0.1.51 - 2026-04-09 ## 0.1.51 - 2026-04-09
### Added ### Added
+1 -1
View File
@@ -42,7 +42,7 @@ buildNpmPackage rec {
# To update: run `nix build` with lib.fakeHash, copy the `got:` hash. # To update: run `nix build` with lib.fakeHash, copy the `got:` hash.
# CI auto-updates this when package-lock.json changes (see .github/workflows/). # CI auto-updates this when package-lock.json changes (see .github/workflows/).
npmDepsHash = "sha256-gyCcVTlgLk8+G/OtwZT/GomN2lYe7uEcDoLp1ZQoc5M="; npmDepsHash = "sha256-6NXZBZwIxCBYxaTe+lq6D8uNaJzL3+z/VUodVS0sS9o=";
# Prevent onnxruntime-node's install script from running during automatic # Prevent onnxruntime-node's install script from running during automatic
# npm rebuild (it tries to download from api.nuget.org, which fails in the sandbox). # npm rebuild (it tries to download from api.nuget.org, which fails in the sandbox).
+19 -19
View File
@@ -1,12 +1,12 @@
{ {
"name": "paseo", "name": "paseo",
"version": "0.1.51", "version": "0.1.52",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "paseo", "name": "paseo",
"version": "0.1.51", "version": "0.1.52",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"workspaces": [ "workspaces": [
@@ -36169,16 +36169,16 @@
}, },
"packages/app": { "packages/app": {
"name": "@getpaseo/app", "name": "@getpaseo/app",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@floating-ui/react-native": "^0.10.7", "@floating-ui/react-native": "^0.10.7",
"@getpaseo/expo-two-way-audio": "0.1.51", "@getpaseo/expo-two-way-audio": "0.1.52",
"@getpaseo/highlight": "0.1.51", "@getpaseo/highlight": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"@gorhom/bottom-sheet": "^5.2.6", "@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14", "@gorhom/portal": "^1.0.14",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
@@ -36295,11 +36295,11 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@getpaseo/cli", "name": "@getpaseo/cli",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"@clack/prompts": "^1.0.0", "@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.51", "@getpaseo/relay": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
@@ -36340,11 +36340,11 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@getpaseo/desktop", "name": "@getpaseo/desktop",
"version": "0.1.51", "version": "0.1.52",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@getpaseo/cli": "0.1.51", "@getpaseo/cli": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"electron-log": "^5.4.3", "electron-log": "^5.4.3",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"ws": "^8.14.2" "ws": "^8.14.2"
@@ -36378,7 +36378,7 @@
}, },
"packages/expo-two-way-audio": { "packages/expo-two-way-audio": {
"name": "@getpaseo/expo-two-way-audio", "name": "@getpaseo/expo-two-way-audio",
"version": "0.1.51", "version": "0.1.52",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
@@ -36579,7 +36579,7 @@
}, },
"packages/highlight": { "packages/highlight": {
"name": "@getpaseo/highlight", "name": "@getpaseo/highlight",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"@lezer/common": "^1.5.0", "@lezer/common": "^1.5.0",
"@lezer/cpp": "^1.1.5", "@lezer/cpp": "^1.1.5",
@@ -36605,7 +36605,7 @@
}, },
"packages/relay": { "packages/relay": {
"name": "@getpaseo/relay", "name": "@getpaseo/relay",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
@@ -36621,14 +36621,14 @@
}, },
"packages/server": { "packages/server": {
"name": "@getpaseo/server", "name": "@getpaseo/server",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"@agentclientprotocol/sdk": "^0.17.1", "@agentclientprotocol/sdk": "^0.17.1",
"@ai-sdk/openai": "2.0.52", "@ai-sdk/openai": "2.0.52",
"@anthropic-ai/claude-agent-sdk": "^0.2.11", "@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@deepgram/sdk": "^3.4.0", "@deepgram/sdk": "^3.4.0",
"@getpaseo/highlight": "0.1.51", "@getpaseo/highlight": "0.1.52",
"@getpaseo/relay": "0.1.51", "@getpaseo/relay": "0.1.52",
"@isaacs/ttlcache": "^2.1.4", "@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1", "@modelcontextprotocol/sdk": "^1.20.1",
"@opencode-ai/sdk": "1.2.6", "@opencode-ai/sdk": "1.2.6",
@@ -37031,7 +37031,7 @@
}, },
"packages/website": { "packages/website": {
"name": "@getpaseo/website", "name": "@getpaseo/website",
"version": "0.1.51", "version": "0.1.52",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "^1.20.3", "@cloudflare/vite-plugin": "^1.20.3",
"@cloudflare/workers-types": "^4.20260114.0", "@cloudflare/workers-types": "^4.20260114.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "paseo", "name": "paseo",
"version": "0.1.51", "version": "0.1.52",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"packages/expo-two-way-audio", "packages/expo-two-way-audio",
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"name": "@getpaseo/app", "name": "@getpaseo/app",
"main": "index.ts", "main": "index.ts",
"version": "0.1.51", "version": "0.1.52",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
@@ -31,9 +31,9 @@
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@floating-ui/react-native": "^0.10.7", "@floating-ui/react-native": "^0.10.7",
"@getpaseo/expo-two-way-audio": "0.1.51", "@getpaseo/expo-two-way-audio": "0.1.52",
"@getpaseo/highlight": "0.1.51", "@getpaseo/highlight": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"@gorhom/bottom-sheet": "^5.2.6", "@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14", "@gorhom/portal": "^1.0.14",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
@@ -1,4 +1,12 @@
import { useCallback, useMemo, useState, type Dispatch, type SetStateAction } from "react"; import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type Dispatch,
type SetStateAction,
} from "react";
import { import {
ActivityIndicator, ActivityIndicator,
Platform, Platform,
@@ -97,6 +105,27 @@ function getFallbackTabLabel(tab: WorkspaceTabDescriptor): string {
return "Agent"; return "Agent";
} }
function useMiddleClickClose(onClose: () => void) {
const ref = useRef<View>(null);
useEffect(() => {
const node = ref.current as unknown as HTMLElement | null;
if (!node) return;
function handleAuxClick(event: MouseEvent) {
if (event.button === 1) {
event.preventDefault();
onClose();
}
}
node.addEventListener("auxclick", handleAuxClick);
return () => node.removeEventListener("auxclick", handleAuxClick);
}, [onClose]);
return ref;
}
function TabChip({ function TabChip({
tab, tab,
isActive, isActive,
@@ -136,6 +165,9 @@ function TabChip({
}) { }) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab;
const middleClickRef = useMiddleClickClose(
useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]),
);
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const isHighlighted = isActive || hovered || isCloseHovered; const isHighlighted = isActive || hovered || isCloseHovered;
const closeButtonDragBlockers = const closeButtonDragBlockers =
@@ -151,6 +183,7 @@ function TabChip({
: undefined; : undefined;
return ( return (
<View ref={middleClickRef}>
<ContextMenu key={tab.key}> <ContextMenu key={tab.key}>
<Tooltip delayDuration={400} enabledOnDesktop enabledOnMobile={false}> <Tooltip delayDuration={400} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild triggerRefProp="triggerRef"> <TooltipTrigger asChild triggerRefProp="triggerRef">
@@ -318,6 +351,7 @@ function TabChip({
)} )}
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</View>
); );
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/cli", "name": "@getpaseo/cli",
"version": "0.1.51", "version": "0.1.52",
"description": "Paseo CLI - control your AI coding agents from the command line", "description": "Paseo CLI - control your AI coding agents from the command line",
"type": "module", "type": "module",
"files": [ "files": [
@@ -24,8 +24,8 @@
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^1.0.0", "@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.51", "@getpaseo/relay": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/desktop", "name": "@getpaseo/desktop",
"version": "0.1.51", "version": "0.1.52",
"private": true, "private": true,
"description": "Paseo desktop app (Electron wrapper)", "description": "Paseo desktop app (Electron wrapper)",
"main": "dist/main.js", "main": "dist/main.js",
@@ -12,8 +12,8 @@
"typecheck": "tsc --noEmit -p tsconfig.json" "typecheck": "tsc --noEmit -p tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@getpaseo/cli": "0.1.51", "@getpaseo/cli": "0.1.52",
"@getpaseo/server": "0.1.51", "@getpaseo/server": "0.1.52",
"electron-log": "^5.4.3", "electron-log": "^5.4.3",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"ws": "^8.14.2" "ws": "^8.14.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/expo-two-way-audio", "name": "@getpaseo/expo-two-way-audio",
"version": "0.1.51", "version": "0.1.52",
"description": "Native module for two way audio streaming", "description": "Native module for two way audio streaming",
"main": "build/index.js", "main": "build/index.js",
"types": "build/index.d.ts", "types": "build/index.d.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/highlight", "name": "@getpaseo/highlight",
"version": "0.1.51", "version": "0.1.52",
"type": "module", "type": "module",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/relay", "name": "@getpaseo/relay",
"version": "0.1.51", "version": "0.1.52",
"description": "Paseo relay for bridging daemon and client connections", "description": "Paseo relay for bridging daemon and client connections",
"type": "module", "type": "module",
"publishConfig": { "publishConfig": {
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/server", "name": "@getpaseo/server",
"version": "0.1.51", "version": "0.1.52",
"description": "Paseo backend server", "description": "Paseo backend server",
"type": "module", "type": "module",
"publishConfig": { "publishConfig": {
@@ -61,8 +61,8 @@
"@ai-sdk/openai": "2.0.52", "@ai-sdk/openai": "2.0.52",
"@anthropic-ai/claude-agent-sdk": "^0.2.11", "@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@deepgram/sdk": "^3.4.0", "@deepgram/sdk": "^3.4.0",
"@getpaseo/highlight": "0.1.51", "@getpaseo/highlight": "0.1.52",
"@getpaseo/relay": "0.1.51", "@getpaseo/relay": "0.1.52",
"@isaacs/ttlcache": "^2.1.4", "@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1", "@modelcontextprotocol/sdk": "^1.20.1",
"@opencode-ai/sdk": "1.2.6", "@opencode-ai/sdk": "1.2.6",
-11
View File
@@ -408,7 +408,6 @@ export type SessionOptions = {
appVersion?: string | null; appVersion?: string | null;
onMessage: (msg: SessionOutboundMessage) => void; onMessage: (msg: SessionOutboundMessage) => void;
onBinaryMessage?: (frame: Uint8Array) => void; onBinaryMessage?: (frame: Uint8Array) => void;
getBinaryBufferedAmount?: () => number;
onLifecycleIntent?: (intent: SessionLifecycleIntent) => void; onLifecycleIntent?: (intent: SessionLifecycleIntent) => void;
logger: pino.Logger; logger: pino.Logger;
downloadTokenStore: DownloadTokenStore; downloadTokenStore: DownloadTokenStore;
@@ -549,7 +548,6 @@ export class Session {
private readonly sessionId: string; private readonly sessionId: string;
private readonly onMessage: (msg: SessionOutboundMessage) => void; private readonly onMessage: (msg: SessionOutboundMessage) => void;
private readonly onBinaryMessage: ((frame: Uint8Array) => void) | null; private readonly onBinaryMessage: ((frame: Uint8Array) => void) | null;
private readonly getBinaryBufferedAmount: (() => number) | null;
private readonly onLifecycleIntent: ((intent: SessionLifecycleIntent) => void) | null; private readonly onLifecycleIntent: ((intent: SessionLifecycleIntent) => void) | null;
private readonly sessionLogger: pino.Logger; private readonly sessionLogger: pino.Logger;
private readonly paseoHome: string; private readonly paseoHome: string;
@@ -661,7 +659,6 @@ export class Session {
appVersion, appVersion,
onMessage, onMessage,
onBinaryMessage, onBinaryMessage,
getBinaryBufferedAmount,
onLifecycleIntent, onLifecycleIntent,
logger, logger,
downloadTokenStore, downloadTokenStore,
@@ -697,7 +694,6 @@ export class Session {
this.sessionId = uuidv4(); this.sessionId = uuidv4();
this.onMessage = onMessage; this.onMessage = onMessage;
this.onBinaryMessage = onBinaryMessage ?? null; this.onBinaryMessage = onBinaryMessage ?? null;
this.getBinaryBufferedAmount = getBinaryBufferedAmount ?? null;
this.onLifecycleIntent = onLifecycleIntent ?? null; this.onLifecycleIntent = onLifecycleIntent ?? null;
this.downloadTokenStore = downloadTokenStore; this.downloadTokenStore = downloadTokenStore;
this.pushTokenStore = pushTokenStore; this.pushTokenStore = pushTokenStore;
@@ -8713,11 +8709,4 @@ export class Session {
} }
} }
private getCurrentBinaryBufferedAmount(): number {
const bufferedAmount = this.getBinaryBufferedAmount?.() ?? 0;
if (!Number.isFinite(bufferedAmount) || bufferedAmount < 0) {
return 0;
}
return Math.floor(bufferedAmount);
}
} }
@@ -673,16 +673,6 @@ export class VoiceAssistantWebSocketServer {
} }
this.sendBinaryToConnection(connection, frame); this.sendBinaryToConnection(connection, frame);
}, },
getBinaryBufferedAmount: () => {
if (!connection) {
return 0;
}
let bufferedAmount = 0;
for (const socket of connection.sockets) {
bufferedAmount = Math.max(bufferedAmount, socket.bufferedAmount ?? 0);
}
return bufferedAmount;
},
onLifecycleIntent: (intent) => { onLifecycleIntent: (intent) => {
this.onLifecycleIntent?.(intent); this.onLifecycleIntent?.(intent);
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/website", "name": "@getpaseo/website",
"version": "0.1.51", "version": "0.1.52",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {