Add screen orientation polyfill, refactor provider launch config, and update desktop runtime manager

- Add .claude schedule tasks to .gitignore
- Add screen-orientation polyfill for Expo web
- Refactor agent provider launch config into shared utility
- Update desktop runtime manager with improved binary management
- Update desktop release workflow
This commit is contained in:
Mohamed Boudra
2026-03-11 14:01:07 +07:00
parent acfb933ee8
commit eb5f011161
13 changed files with 264 additions and 53 deletions
@@ -1,5 +1,6 @@
import { execFileSync } from "node:child_process";
import { execFileSync, execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { platform } from "node:os";
import { z } from "zod";
import type { AgentProvider } from "./agent-sdk-types.js";
@@ -88,24 +89,63 @@ export function applyProviderEnv(
};
}
export function isCommandAvailable(command: string): boolean {
const normalized = command.trim();
if (!normalized) {
return false;
/**
* Resolve an executable name to its absolute path the way the user's shell would.
*
* On Unix we first try `$SHELL -lic "which <name>"` so that rc-file PATH
* additions (asdf, nvm, homebrew, nix, etc.) are visible — exactly as if the
* user opened a terminal and typed the command. If that fails (e.g. the login
* shell itself errors) we fall back to a plain `which`.
*
* On Windows the system PATH is always available, so `where.exe` is sufficient.
*/
export function findExecutable(name: string): string | null {
const trimmed = name.trim();
if (!trimmed) {
return null;
}
if (normalized.includes("/") || normalized.includes("\\")) {
return existsSync(normalized);
if (trimmed.includes("/") || trimmed.includes("\\")) {
return existsSync(trimmed) ? trimmed : null;
}
if (platform() === "win32") {
try {
const out = execSync(`where.exe ${trimmed}`, { encoding: "utf8" }).trim();
const firstLine = out.split(/\r?\n/)[0]?.trim();
return firstLine || null;
} catch {
return null;
}
}
// Unix: try the user's login shell so rc-file PATH entries are visible.
const shell = process.env["SHELL"];
if (shell) {
try {
const out = execSync(`${shell} -lic "which ${trimmed}"`, {
encoding: "utf8",
timeout: 5000,
}).trim();
if (out) {
return out;
}
} catch {
// Login shell failed (broken rc, etc.) — fall through to plain which.
}
}
try {
const output = execFileSync("which", [normalized], { encoding: "utf8" }).trim();
return output.length > 0;
return execFileSync("which", [trimmed], { encoding: "utf8" }).trim() || null;
} catch {
return false;
return null;
}
}
export function isCommandAvailable(command: string): boolean {
return findExecutable(command) !== null;
}
export function isProviderCommandAvailable(
commandConfig: ProviderCommand | undefined,
resolveDefaultCommand: () => string
@@ -69,6 +69,7 @@ import type {
} from "../agent-sdk-types.js";
import {
applyProviderEnv,
findExecutable,
isProviderCommandAvailable,
type ProviderRuntimeSettings,
} from "../provider-launch-config.js";
@@ -363,16 +364,8 @@ type ClaudeAgentSessionOptions = {
logger: Logger;
};
function whichClaude(): string | null {
try {
return execSync("which claude", { encoding: "utf8" }).trim() || null;
} catch {
return null;
}
}
function resolveClaudeBinary(): string {
const claudePath = whichClaude();
const claudePath = findExecutable("claude");
if (claudePath) {
return claudePath;
}
@@ -1414,7 +1407,7 @@ export class ClaudeAgentClient implements AgentClient {
this.defaults = options.defaults;
this.logger = options.logger.child({ module: "agent", provider: "claude" });
this.runtimeSettings = options.runtimeSettings;
this.claudePath = whichClaude();
this.claudePath = findExecutable("claude");
if (this.claudePath) {
try {
const version = execSync(`${this.claudePath} --version`, { encoding: "utf8" }).trim();
@@ -40,6 +40,7 @@ import {
} from "./codex/tool-call-mapper.js";
import {
applyProviderEnv,
findExecutable,
isProviderCommandAvailable,
resolveProviderCommandPrefix,
type ProviderRuntimeSettings,
@@ -150,13 +151,9 @@ function mergeCodexConfiguredDefaults(
}
function resolveCodexBinary(): string {
try {
const codexPath = execSync("which codex", { encoding: "utf8" }).trim();
if (codexPath) {
return codexPath;
}
} catch {
// Fall through to error
const codexPath = findExecutable("codex");
if (codexPath) {
return codexPath;
}
throw new Error(
"Codex CLI not found. Please install codex globally: npm install -g @openai/codex"
@@ -1,4 +1,4 @@
import { execSync, spawn, type ChildProcess } from "node:child_process";
import { spawn, type ChildProcess } from "node:child_process";
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2/client";
import net from "node:net";
import type { Logger } from "pino";
@@ -29,6 +29,7 @@ import type {
} from "../agent-sdk-types.js";
import {
applyProviderEnv,
findExecutable,
isProviderCommandAvailable,
resolveProviderCommandPrefix,
type ProviderRuntimeSettings,
@@ -150,13 +151,9 @@ const OpencodeToolPartToTimelineItemSchema = OpencodeToolPartTimelineEnvelopeSch
);
function resolveOpenCodeBinary(): string {
try {
const opencodePath = execSync("which opencode", { encoding: "utf8" }).trim();
if (opencodePath) {
return opencodePath;
}
} catch {
// fall through
const opencodePath = findExecutable("opencode");
if (opencodePath) {
return opencodePath;
}
throw new Error(
"OpenCode CLI not found. Please install opencode globally so Paseo can launch the provider."