Compare commits

...

3 Commits

Author SHA1 Message Date
Mohamed Boudra
aa1dc3ac21 chore(release): cut 0.1.29 2026-03-18 14:33:44 +07:00
Mohamed Boudra
00659bc32d docs(changelog): note 0.1.29 hotfix 2026-03-18 14:33:35 +07:00
Mohamed Boudra
6c11a082c5 Fix agent spawn PATH resolution and add provider binary checks to status
Shell env PATH was being overridden by process.env PATH in
applyProviderEnv, causing agent spawns to fail with ENOENT when the
daemon runs from the Tauri desktop app (minimal GUI PATH). Flip the
merge order so login shell env wins.

Add a Providers section to `paseo daemon status` that resolves each
agent binary (claude, codex, opencode) and runs --version using the
same applyProviderEnv environment the daemon uses to spawn agents.
2026-03-18 14:31:16 +07:00
17 changed files with 127 additions and 40 deletions

View File

@@ -1,5 +1,13 @@
# Changelog
## 0.1.29 - 2026-03-18
### Improved
- Improved `paseo daemon status` with provider binary resolution and version checks for Claude, Codex, and OpenCode.
### Fixed
- Fixed desktop-managed agent startup failures caused by the daemon using the GUI process PATH instead of the user's login shell PATH.
## 0.1.28 - 2026-03-15
### Added

28
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "paseo",
"version": "0.1.28",
"version": "0.1.29",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "paseo",
"version": "0.1.28",
"version": "0.1.29",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"workspaces": [
@@ -31450,15 +31450,15 @@
},
"packages/app": {
"name": "@getpaseo/app",
"version": "0.1.28",
"version": "0.1.29",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@expo/vector-icons": "^15.0.2",
"@floating-ui/react-native": "^0.10.7",
"@getpaseo/expo-two-way-audio": "0.1.28",
"@getpaseo/server": "0.1.28",
"@getpaseo/expo-two-way-audio": "0.1.29",
"@getpaseo/server": "0.1.29",
"@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14",
"@lezer/common": "^1.5.0",
@@ -31576,11 +31576,11 @@
},
"packages/cli": {
"name": "@getpaseo/cli",
"version": "0.1.28",
"version": "0.1.29",
"dependencies": {
"@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.28",
"@getpaseo/server": "0.1.28",
"@getpaseo/relay": "0.1.29",
"@getpaseo/server": "0.1.29",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"mime-types": "^2.1.35",
@@ -31617,14 +31617,14 @@
},
"packages/desktop": {
"name": "@getpaseo/desktop",
"version": "0.1.28",
"version": "0.1.29",
"devDependencies": {
"@tauri-apps/cli": "^2.9.6"
}
},
"packages/expo-two-way-audio": {
"name": "@getpaseo/expo-two-way-audio",
"version": "0.1.28",
"version": "0.1.29",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.9.4",
@@ -31661,7 +31661,7 @@
},
"packages/relay": {
"name": "@getpaseo/relay",
"version": "0.1.28",
"version": "0.1.29",
"dependencies": {
"base64-js": "^1.5.1",
"tweetnacl": "^1.0.3",
@@ -31677,12 +31677,12 @@
},
"packages/server": {
"name": "@getpaseo/server",
"version": "0.1.28",
"version": "0.1.29",
"dependencies": {
"@ai-sdk/openai": "2.0.52",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@deepgram/sdk": "^3.4.0",
"@getpaseo/relay": "0.1.28",
"@getpaseo/relay": "0.1.29",
"@lezer/common": "^1.5.0",
"@lezer/css": "^1.3.0",
"@lezer/highlight": "^1.2.3",
@@ -32039,7 +32039,7 @@
},
"packages/website": {
"name": "@getpaseo/website",
"version": "0.1.28",
"version": "0.1.29",
"dependencies": {
"@cloudflare/vite-plugin": "^1.20.3",
"@cloudflare/workers-types": "^4.20260114.0",

View File

@@ -1,6 +1,6 @@
{
"name": "paseo",
"version": "0.1.28",
"version": "0.1.29",
"private": true,
"workspaces": [
"packages/expo-two-way-audio",

View File

@@ -1,7 +1,7 @@
{
"name": "@getpaseo/app",
"main": "index.ts",
"version": "0.1.28",
"version": "0.1.29",
"private": true,
"scripts": {
"start": "expo start",
@@ -26,13 +26,13 @@
"deploy:web": "npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main"
},
"dependencies": {
"@getpaseo/expo-two-way-audio": "0.1.28",
"@getpaseo/expo-two-way-audio": "0.1.29",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@expo/vector-icons": "^15.0.2",
"@floating-ui/react-native": "^0.10.7",
"@getpaseo/server": "0.1.28",
"@getpaseo/server": "0.1.29",
"@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14",
"@lezer/common": "^1.5.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/cli",
"version": "0.1.28",
"version": "0.1.29",
"description": "Paseo CLI - control your AI coding agents from the command line",
"type": "module",
"files": [
@@ -24,8 +24,8 @@
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.28",
"@getpaseo/server": "0.1.28",
"@getpaseo/relay": "0.1.29",
"@getpaseo/server": "0.1.29",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"mime-types": "^2.1.35",

View File

@@ -1,11 +1,18 @@
import type { Command } from 'commander'
import { execFileSync } from 'node:child_process'
import { createRequire } from 'node:module'
import { getOrCreateServerId } from '@getpaseo/server'
import { getOrCreateServerId, findExecutable, applyProviderEnv } from '@getpaseo/server'
import { tryConnectToDaemon } from '../../utils/client.js'
import type { CommandOptions, ListResult, OutputSchema } from '../../output/index.js'
import { resolveLocalDaemonState, resolveTcpHostFromListen } from './local-daemon.js'
import { resolveNodePathFromPid } from './runtime-toolchain.js'
interface ProviderBinaryStatus {
label: string
path: string | null
version: string | null
}
interface DaemonStatus {
serverId: string | null
status: 'running' | 'stopped' | 'unresponsive'
@@ -21,6 +28,7 @@ interface DaemonStatus {
daemonNode: string
cliNode: string
cliVersion: string
providers: ProviderBinaryStatus[]
note?: string
}
@@ -77,16 +85,17 @@ function createStatusSchema(status: DaemonStatus): OutputSchema<StatusRow> {
header: 'VALUE',
field: 'value',
color: (_, item) => {
if (item.key !== 'Status') {
return undefined
if (item.key === 'Status') {
if (item.value === 'running') return 'green'
if (item.value === 'unresponsive') return 'yellow'
return 'red'
}
if (item.value === 'running') {
if (item.key.startsWith(' ')) {
if (item.value === 'not found') return 'red'
if (item.value.endsWith('(--version failed)')) return 'yellow'
return 'green'
}
if (item.value === 'unresponsive') {
return 'yellow'
}
return 'red'
return undefined
},
},
],
@@ -126,9 +135,53 @@ function toStatusRows(status: DaemonStatus): StatusRow[] {
rows.push({ key: 'Note', value: status.note })
}
rows.push({ key: '', value: '' })
rows.push({ key: 'Providers', value: '' })
for (const provider of status.providers) {
if (!provider.path) {
rows.push({ key: ` ${provider.label}`, value: 'not found' })
} else if (!provider.version) {
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (--version failed)` })
} else {
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (${provider.version})` })
}
}
return rows
}
const PROVIDER_BINARIES: { label: string; binary: string }[] = [
{ label: 'Claude', binary: 'claude' },
{ label: 'Codex', binary: 'codex' },
{ label: 'OpenCode', binary: 'opencode' },
]
function checkProviderBinary(binary: string): { path: string | null; version: string | null } {
const binaryPath = findExecutable(binary)
if (!binaryPath) {
return { path: null, version: null }
}
const env = applyProviderEnv(process.env)
try {
const output = execFileSync(binaryPath, ['--version'], {
encoding: 'utf8',
timeout: 5000,
stdio: ['ignore', 'pipe', 'pipe'],
env,
}).trim()
return { path: binaryPath, version: output || null }
} catch {
return { path: binaryPath, version: null }
}
}
function checkProviderBinaries(): ProviderBinaryStatus[] {
return PROVIDER_BINARIES.map(({ label, binary }) => {
const result = checkProviderBinary(binary)
return { label, ...result }
})
}
function resolveOwnerLabel(uid: number | undefined, hostname: string | undefined): string | null {
if (uid === undefined && !hostname) {
return null
@@ -201,6 +254,8 @@ export async function runStatusCommand(
note = appendNote(note, `serverId unavailable: ${shortenMessage(normalizeError(error))}`)
}
const providers = checkProviderBinaries()
const daemonStatus: DaemonStatus = {
serverId,
status,
@@ -216,6 +271,7 @@ export async function runStatusCommand(
daemonNode,
cliNode,
cliVersion,
providers,
note,
}

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/desktop",
"version": "0.1.28",
"version": "0.1.29",
"private": true,
"description": "Paseo desktop app (Tauri wrapper)",
"scripts": {

View File

@@ -1,6 +1,6 @@
[package]
name = "paseo"
version = "0.1.28"
version = "0.1.29"
description = "Paseo Desktop"
authors = ["moboudra"]
license = "MIT"
@@ -54,3 +54,4 @@ mac-notification-sys = "0.6"

View File

@@ -7,9 +7,9 @@
<key>CFBundleName</key>
<string>Paseo</string>
<key>CFBundleShortVersionString</key>
<string>0.1.28</string>
<string>0.1.29</string>
<key>CFBundleVersion</key>
<string>0.1.28</string>
<string>0.1.29</string>
<key>NSMicrophoneUsageDescription</key>
<string>Paseo needs access to your microphone for voice dictation and voice mode.</string>
</dict>

View File

@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Paseo",
"version": "0.1.28",
"version": "0.1.29",
"identifier": "dev.paseo.desktop",
"build": {
"frontendDist": "../../app/dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/expo-two-way-audio",
"version": "0.1.28",
"version": "0.1.29",
"description": "Native module for two way audio streaming",
"main": "build/index.js",
"types": "build/index.d.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/relay",
"version": "0.1.28",
"version": "0.1.29",
"description": "Paseo relay for bridging daemon and client connections",
"type": "module",
"publishConfig": {

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/server",
"version": "0.1.28",
"version": "0.1.29",
"description": "Paseo backend server",
"type": "module",
"publishConfig": {
@@ -64,7 +64,7 @@
"@ai-sdk/openai": "2.0.52",
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
"@deepgram/sdk": "^3.4.0",
"@getpaseo/relay": "0.1.28",
"@getpaseo/relay": "0.1.29",
"@lezer/common": "^1.5.0",
"@lezer/css": "^1.3.0",
"@lezer/highlight": "^1.2.3",

View File

@@ -92,6 +92,25 @@ describe("applyProviderEnv", () => {
expect(env.FOO).toBe("bar");
expect(Object.keys(env).length).toBeGreaterThanOrEqual(3);
});
test("shell env PATH wins over base env PATH", () => {
const base = { PATH: "/usr/bin:/bin" };
const shellEnv = { PATH: "/usr/local/bin:/usr/bin:/bin:/home/user/.nvm/bin" };
const env = applyProviderEnv(base, undefined, shellEnv);
expect(env.PATH).toBe("/usr/local/bin:/usr/bin:/bin:/home/user/.nvm/bin");
});
test("runtimeSettings env wins over shell env", () => {
const base = { PATH: "/usr/bin" };
const shellEnv = { PATH: "/usr/local/bin:/usr/bin" };
const runtime: ProviderRuntimeSettings = { env: { PATH: "/custom/path" } };
const env = applyProviderEnv(base, runtime, shellEnv);
expect(env.PATH).toBe("/custom/path");
});
});
describe("findExecutable", () => {

View File

@@ -131,8 +131,8 @@ export function applyProviderEnv(
shellEnv?: Record<string, string>
): Record<string, string | undefined> {
return {
...(shellEnv ?? resolveShellEnv()),
...baseEnv,
...(shellEnv ?? resolveShellEnv()),
...(runtimeSettings?.env ?? {}),
};
}

View File

@@ -24,6 +24,9 @@ export {
type SherpaLoaderEnvResolution,
} from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
// Provider binary resolution
export { findExecutable, applyProviderEnv } from "./agent/provider-launch-config.js";
// Agent SDK types for CLI commands
export type {
AgentMode,

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/website",
"version": "0.1.28",
"version": "0.1.29",
"private": true,
"type": "module",
"scripts": {