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 # 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 ## 0.1.28 - 2026-03-15
### Added ### Added

28
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "paseo", "name": "paseo",
"version": "0.1.28", "version": "0.1.29",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "paseo", "name": "paseo",
"version": "0.1.28", "version": "0.1.29",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"workspaces": [ "workspaces": [
@@ -31450,15 +31450,15 @@
}, },
"packages/app": { "packages/app": {
"name": "@getpaseo/app", "name": "@getpaseo/app",
"version": "0.1.28", "version": "0.1.29",
"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.28", "@getpaseo/expo-two-way-audio": "0.1.29",
"@getpaseo/server": "0.1.28", "@getpaseo/server": "0.1.29",
"@gorhom/bottom-sheet": "^5.2.6", "@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14", "@gorhom/portal": "^1.0.14",
"@lezer/common": "^1.5.0", "@lezer/common": "^1.5.0",
@@ -31576,11 +31576,11 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@getpaseo/cli", "name": "@getpaseo/cli",
"version": "0.1.28", "version": "0.1.29",
"dependencies": { "dependencies": {
"@clack/prompts": "^1.0.0", "@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.28", "@getpaseo/relay": "0.1.29",
"@getpaseo/server": "0.1.28", "@getpaseo/server": "0.1.29",
"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",
@@ -31617,14 +31617,14 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@getpaseo/desktop", "name": "@getpaseo/desktop",
"version": "0.1.28", "version": "0.1.29",
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2.9.6" "@tauri-apps/cli": "^2.9.6"
} }
}, },
"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.28", "version": "0.1.29",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
@@ -31661,7 +31661,7 @@
}, },
"packages/relay": { "packages/relay": {
"name": "@getpaseo/relay", "name": "@getpaseo/relay",
"version": "0.1.28", "version": "0.1.29",
"dependencies": { "dependencies": {
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
@@ -31677,12 +31677,12 @@
}, },
"packages/server": { "packages/server": {
"name": "@getpaseo/server", "name": "@getpaseo/server",
"version": "0.1.28", "version": "0.1.29",
"dependencies": { "dependencies": {
"@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/relay": "0.1.28", "@getpaseo/relay": "0.1.29",
"@lezer/common": "^1.5.0", "@lezer/common": "^1.5.0",
"@lezer/css": "^1.3.0", "@lezer/css": "^1.3.0",
"@lezer/highlight": "^1.2.3", "@lezer/highlight": "^1.2.3",
@@ -32039,7 +32039,7 @@
}, },
"packages/website": { "packages/website": {
"name": "@getpaseo/website", "name": "@getpaseo/website",
"version": "0.1.28", "version": "0.1.29",
"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",

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"name": "@getpaseo/app", "name": "@getpaseo/app",
"main": "index.ts", "main": "index.ts",
"version": "0.1.28", "version": "0.1.29",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
@@ -26,13 +26,13 @@
"deploy:web": "npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main" "deploy:web": "npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main"
}, },
"dependencies": { "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/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/server": "0.1.28", "@getpaseo/server": "0.1.29",
"@gorhom/bottom-sheet": "^5.2.6", "@gorhom/bottom-sheet": "^5.2.6",
"@gorhom/portal": "^1.0.14", "@gorhom/portal": "^1.0.14",
"@lezer/common": "^1.5.0", "@lezer/common": "^1.5.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/cli", "name": "@getpaseo/cli",
"version": "0.1.28", "version": "0.1.29",
"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.28", "@getpaseo/relay": "0.1.29",
"@getpaseo/server": "0.1.28", "@getpaseo/server": "0.1.29",
"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",

View File

@@ -1,11 +1,18 @@
import type { Command } from 'commander' import type { Command } from 'commander'
import { execFileSync } from 'node:child_process'
import { createRequire } from 'node:module' import { createRequire } from 'node:module'
import { getOrCreateServerId } from '@getpaseo/server' import { getOrCreateServerId, findExecutable, applyProviderEnv } from '@getpaseo/server'
import { tryConnectToDaemon } from '../../utils/client.js' import { tryConnectToDaemon } from '../../utils/client.js'
import type { CommandOptions, ListResult, OutputSchema } from '../../output/index.js' import type { CommandOptions, ListResult, OutputSchema } from '../../output/index.js'
import { resolveLocalDaemonState, resolveTcpHostFromListen } from './local-daemon.js' import { resolveLocalDaemonState, resolveTcpHostFromListen } from './local-daemon.js'
import { resolveNodePathFromPid } from './runtime-toolchain.js' import { resolveNodePathFromPid } from './runtime-toolchain.js'
interface ProviderBinaryStatus {
label: string
path: string | null
version: string | null
}
interface DaemonStatus { interface DaemonStatus {
serverId: string | null serverId: string | null
status: 'running' | 'stopped' | 'unresponsive' status: 'running' | 'stopped' | 'unresponsive'
@@ -21,6 +28,7 @@ interface DaemonStatus {
daemonNode: string daemonNode: string
cliNode: string cliNode: string
cliVersion: string cliVersion: string
providers: ProviderBinaryStatus[]
note?: string note?: string
} }
@@ -77,16 +85,17 @@ function createStatusSchema(status: DaemonStatus): OutputSchema<StatusRow> {
header: 'VALUE', header: 'VALUE',
field: 'value', field: 'value',
color: (_, item) => { color: (_, item) => {
if (item.key !== 'Status') { if (item.key === 'Status') {
return undefined 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' return 'green'
} }
if (item.value === 'unresponsive') { return undefined
return 'yellow'
}
return 'red'
}, },
}, },
], ],
@@ -126,9 +135,53 @@ function toStatusRows(status: DaemonStatus): StatusRow[] {
rows.push({ key: 'Note', value: status.note }) 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 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 { function resolveOwnerLabel(uid: number | undefined, hostname: string | undefined): string | null {
if (uid === undefined && !hostname) { if (uid === undefined && !hostname) {
return null return null
@@ -201,6 +254,8 @@ export async function runStatusCommand(
note = appendNote(note, `serverId unavailable: ${shortenMessage(normalizeError(error))}`) note = appendNote(note, `serverId unavailable: ${shortenMessage(normalizeError(error))}`)
} }
const providers = checkProviderBinaries()
const daemonStatus: DaemonStatus = { const daemonStatus: DaemonStatus = {
serverId, serverId,
status, status,
@@ -216,6 +271,7 @@ export async function runStatusCommand(
daemonNode, daemonNode,
cliNode, cliNode,
cliVersion, cliVersion,
providers,
note, note,
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/expo-two-way-audio", "name": "@getpaseo/expo-two-way-audio",
"version": "0.1.28", "version": "0.1.29",
"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",

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@getpaseo/server", "name": "@getpaseo/server",
"version": "0.1.28", "version": "0.1.29",
"description": "Paseo backend server", "description": "Paseo backend server",
"type": "module", "type": "module",
"publishConfig": { "publishConfig": {
@@ -64,7 +64,7 @@
"@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/relay": "0.1.28", "@getpaseo/relay": "0.1.29",
"@lezer/common": "^1.5.0", "@lezer/common": "^1.5.0",
"@lezer/css": "^1.3.0", "@lezer/css": "^1.3.0",
"@lezer/highlight": "^1.2.3", "@lezer/highlight": "^1.2.3",

View File

@@ -92,6 +92,25 @@ describe("applyProviderEnv", () => {
expect(env.FOO).toBe("bar"); expect(env.FOO).toBe("bar");
expect(Object.keys(env).length).toBeGreaterThanOrEqual(3); 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", () => { describe("findExecutable", () => {

View File

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

View File

@@ -24,6 +24,9 @@ export {
type SherpaLoaderEnvResolution, type SherpaLoaderEnvResolution,
} from "./speech/providers/local/sherpa/sherpa-runtime-env.js"; } 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 // Agent SDK types for CLI commands
export type { export type {
AgentMode, AgentMode,

View File

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