Merge branch 'main' into dev

Surgical merge of 74 commits from main into dev. Key features ported:

- Provider visibility gating (appVersion filtering for Pi/Copilot)
- Pi agent provider + Copilot re-enabled
- Provider-declared features system (Codex fast mode)
- Workspace dedup by worktree root (adapted to integer IDs)
- Bulk close archiving fix for stored agents
- Agent creation timeout increase to 60s
- Audio/voice crash fixes (external buffer copies)
- Multi-host setup fix
- Reload agent tab action

Dev architecture preserved: SQLite storage, service proxy,
workspace setup dialog, hover cards, removed launcher tabs.
This commit is contained in:
Mohamed Boudra
2026-04-05 12:59:07 +07:00
173 changed files with 8873 additions and 2316 deletions
+24 -42
View File
@@ -12,7 +12,9 @@ import {
import { join } from "path";
import { tmpdir } from "os";
import {
__resetGhPathCacheForTests,
__resetPullRequestStatusCacheForTests,
__setGhPathForTests,
__setPullRequestStatusCacheTtlForTests,
commitAll,
getCheckoutDiff,
@@ -62,10 +64,12 @@ describe("checkout git utilities", () => {
tempDir = setup.tempDir;
repoDir = setup.repoDir;
paseoHome = join(tempDir, "paseo-home");
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
});
afterEach(() => {
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
rmSync(tempDir, { recursive: true, force: true });
});
@@ -147,6 +151,7 @@ const x = 1;
const status = await getCheckoutStatusLite(repoDir);
expect(status.isGit).toBe(true);
expect(status.currentBranch).toBe("main");
expect(status.worktreeRoot).toBe(repoDir);
expect(status.isPaseoOwnedWorktree).toBe(false);
expect(status.mainRepoRoot).toBeNull();
});
@@ -369,6 +374,7 @@ const x = 1;
const status = await getCheckoutStatusLite(result.worktreePath, { paseoHome });
expect(status.isGit).toBe(true);
expect(status.worktreeRoot).toBe(result.worktreePath);
expect(status.isPaseoOwnedWorktree).toBe(true);
expect(status.mainRepoRoot).toBe(repoDir);
});
@@ -582,18 +588,14 @@ const x = 1;
const gitPath = execSync("command -v git", { stdio: "pipe" }).toString().trim();
symlinkSync(gitPath, join(fakeBinDir, "git"));
const originalPath = process.env.PATH;
process.env.PATH = fakeBinDir;
try {
__setGhPathForTests(null);
const status = await getPullRequestStatus(repoDir);
expect(status.githubFeaturesEnabled).toBe(false);
expect(status.status).toBeNull();
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
@@ -627,9 +629,8 @@ const x = 1;
);
execSync(`chmod +x ${join(fakeBinDir, "gh")}`);
const originalPath = process.env.PATH;
process.env.PATH = `${fakeBinDir}:${originalPath ?? ""}`;
try {
__setGhPathForTests(join(fakeBinDir, "gh"));
const status = await getPullRequestStatus(repoDir);
expect(status.githubFeaturesEnabled).toBe(true);
expect(status.status).not.toBeNull();
@@ -639,11 +640,8 @@ const x = 1;
expect(status.status?.isMerged).toBe(true);
expect(status.status?.state).toBe("merged");
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
@@ -677,9 +675,8 @@ const x = 1;
);
execSync(`chmod +x ${join(fakeBinDir, "gh")}`);
const originalPath = process.env.PATH;
process.env.PATH = `${fakeBinDir}:${originalPath ?? ""}`;
try {
__setGhPathForTests(join(fakeBinDir, "gh"));
const status = await getPullRequestStatus(repoDir);
expect(status.githubFeaturesEnabled).toBe(true);
expect(status.status).not.toBeNull();
@@ -689,11 +686,8 @@ const x = 1;
expect(status.status?.isMerged).toBe(false);
expect(status.status?.state).toBe("closed");
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
@@ -732,20 +726,16 @@ const x = 1;
);
execSync(`chmod +x ${join(fakeBinDir, "gh")}`);
const originalPath = process.env.PATH;
process.env.PATH = `${fakeBinDir}:${originalPath ?? ""}`;
try {
__setGhPathForTests(join(fakeBinDir, "gh"));
const first = await getPullRequestStatus(repoDir);
const second = await getPullRequestStatus(repoDir);
expect(first).toEqual(second);
expect(first.status?.url).toContain("/pull/123");
expect(readFileSync(callCountPath, "utf8").trim()).toBe("1");
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
@@ -785,10 +775,9 @@ const x = 1;
);
execSync(`chmod +x ${join(fakeBinDir, "gh")}`);
const originalPath = process.env.PATH;
process.env.PATH = `${fakeBinDir}:${originalPath ?? ""}`;
__setPullRequestStatusCacheTtlForTests(50);
try {
__setGhPathForTests(join(fakeBinDir, "gh"));
const first = await getPullRequestStatus(repoDir);
await sleep(80);
const second = await getPullRequestStatus(repoDir);
@@ -796,11 +785,8 @@ const x = 1;
expect(second.status?.url).toContain("/pull/2");
expect(readFileSync(callCountPath, "utf8").trim()).toBe("2");
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
@@ -840,9 +826,8 @@ const x = 1;
);
execSync(`chmod +x ${join(fakeBinDir, "gh")}`);
const originalPath = process.env.PATH;
process.env.PATH = `${fakeBinDir}:${originalPath ?? ""}`;
try {
__setGhPathForTests(join(fakeBinDir, "gh"));
const [first, second] = await Promise.all([
getPullRequestStatus(repoDir),
getPullRequestStatus(repoDir),
@@ -850,11 +835,8 @@ const x = 1;
expect(first).toEqual(second);
expect(readFileSync(callCountPath, "utf8").trim()).toBe("1");
} finally {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
__resetGhPathCacheForTests();
__resetPullRequestStatusCacheForTests();
}
});
+30 -10
View File
@@ -6,6 +6,7 @@ import { open as openFile, stat as statFile } from "fs/promises";
import { TTLCache } from "@isaacs/ttlcache";
import type { ParsedDiffFile } from "../server/utils/diff-highlighter.js";
import { parseAndHighlightDiff } from "../server/utils/diff-highlighter.js";
import { findExecutable, resolveShellEnv } from "./executable.js";
import { isPaseoOwnedWorktreeCwd } from "./worktree.js";
import { requirePaseoWorktreeBaseRefName } from "./worktree-metadata.js";
@@ -23,6 +24,7 @@ const PULL_REQUEST_STATUS_CACHE_MAX = 1_000;
let pullRequestStatusCacheTtlMs = DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS;
let pullRequestStatusCache = createPullRequestStatusCache(pullRequestStatusCacheTtlMs);
const pullRequestStatusInFlight = new Map<string, Promise<PullRequestStatusResult>>();
let cachedGhPath: string | null | undefined = undefined;
function createPullRequestStatusCache(ttlMs: number) {
return new TTLCache<string, PullRequestStatusResult>({
@@ -52,6 +54,14 @@ export function __setPullRequestStatusCacheTtlForTests(ttlMs: number): void {
pullRequestStatusInFlight.clear();
}
export function __resetGhPathCacheForTests(): void {
cachedGhPath = undefined;
}
export function __setGhPathForTests(path: string | null): void {
cachedGhPath = path;
}
async function execGit(
command: string,
options: { cwd: string; env?: NodeJS.ProcessEnv },
@@ -532,6 +542,7 @@ export type CheckoutStatusLiteNotGit = {
isGit: false;
currentBranch: null;
remoteUrl: null;
worktreeRoot: null;
isPaseoOwnedWorktree: false;
mainRepoRoot: null;
};
@@ -540,6 +551,7 @@ export type CheckoutStatusLiteGitNonPaseo = {
isGit: true;
currentBranch: string | null;
remoteUrl: string | null;
worktreeRoot: string;
isPaseoOwnedWorktree: false;
mainRepoRoot: null;
};
@@ -548,6 +560,7 @@ export type CheckoutStatusLiteGitPaseo = {
isGit: true;
currentBranch: string | null;
remoteUrl: string | null;
worktreeRoot: string;
isPaseoOwnedWorktree: true;
mainRepoRoot: string;
};
@@ -1149,6 +1162,7 @@ export async function getCheckoutStatusLite(
isGit: false,
currentBranch: null,
remoteUrl: null,
worktreeRoot: null,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
@@ -1159,6 +1173,7 @@ export async function getCheckoutStatusLite(
isGit: true,
currentBranch: inspected.currentBranch,
remoteUrl: inspected.remoteUrl,
worktreeRoot: inspected.worktreeRoot,
isPaseoOwnedWorktree: true,
mainRepoRoot: await getMainRepoRoot(cwd),
};
@@ -1168,6 +1183,7 @@ export async function getCheckoutStatusLite(
isGit: true,
currentBranch: inspected.currentBranch,
remoteUrl: inspected.remoteUrl,
worktreeRoot: inspected.worktreeRoot,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
@@ -1731,12 +1747,14 @@ export interface PullRequestStatusResult {
githubFeaturesEnabled: boolean;
}
async function ensureGhAvailable(cwd: string): Promise<void> {
try {
await execAsync("gh --version", { cwd });
} catch {
throw new Error("GitHub CLI (gh) is not available or not authenticated");
function resolveGhPath(): string {
if (cachedGhPath === undefined) {
cachedGhPath = findExecutable("gh");
}
if (cachedGhPath === null) {
throw new Error("GitHub CLI (gh) is not installed or not in PATH");
}
return cachedGhPath;
}
function getCommandErrorText(error: unknown): string {
@@ -1805,7 +1823,7 @@ export async function createPullRequest(
options: CreatePullRequestOptions,
): Promise<{ url: string; number: number }> {
await requireGitRepo(cwd);
await ensureGhAvailable(cwd);
const ghPath = resolveGhPath();
const repo = await resolveGitHubRepo(cwd);
if (!repo) {
throw new Error("Unable to determine GitHub repo from git remote");
@@ -1827,13 +1845,14 @@ export async function createPullRequest(
await execAsync(`git push -u origin ${head}`, { cwd });
const ghEnv = { ...resolveShellEnv(), GIT_TERMINAL_PROMPT: "0" };
const args = ["api", "-X", "POST", `repos/${repo}/pulls`, "-f", `title=${options.title}`];
args.push("-f", `head=${head}`);
args.push("-f", `base=${normalizedBase}`);
if (options.body) {
args.push("-f", `body=${options.body}`);
}
const { stdout } = await execFileAsync("gh", args, { cwd });
const { stdout } = await execFileAsync(ghPath, args, { cwd, env: ghEnv });
const parsed = JSON.parse(stdout.trim());
if (!parsed?.url || !parsed?.number) {
throw new Error("GitHub CLI did not return PR url/number");
@@ -1875,8 +1894,9 @@ async function getPullRequestStatusUncached(cwd: string): Promise<PullRequestSta
githubFeaturesEnabled: false,
};
}
let ghPath: string;
try {
await ensureGhAvailable(cwd);
ghPath = resolveGhPath();
} catch {
return {
status: null,
@@ -1885,14 +1905,14 @@ async function getPullRequestStatusUncached(cwd: string): Promise<PullRequestSta
}
try {
const { stdout } = await execFileAsync(
"gh",
ghPath,
[
"pr",
"view",
"--json",
"url,title,state,baseRefName,headRefName,mergedAt",
],
{ cwd },
{ cwd, env: { ...resolveShellEnv(), GIT_TERMINAL_PROMPT: "0" } },
);
const pr = JSON.parse(stdout.trim());
if (!pr || typeof pr !== "object" || !pr.url || !pr.title) {
@@ -0,0 +1,183 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import {
findExecutable,
quoteWindowsArgument,
quoteWindowsCommand,
} from "./executable.js";
type FindExecutableDependencies = NonNullable<Parameters<typeof findExecutable>[1]>;
function createFindExecutableDependencies(): FindExecutableDependencies {
return {
execFileSync: vi.fn(),
execSync: vi.fn(),
existsSync: vi.fn(),
platform: vi.fn(() => "darwin"),
shell: undefined,
};
}
let findExecutableDependencies: FindExecutableDependencies;
beforeEach(() => {
findExecutableDependencies = createFindExecutableDependencies();
});
describe("findExecutable", () => {
test("on Windows, resolves executables using current machine and user PATH entries", () => {
findExecutableDependencies.platform = vi.fn(() => "win32");
process.env.Path = "C:\\Windows\\System32";
findExecutableDependencies.execFileSync.mockImplementation(
((command: string, args?: string[]) => {
if (command === "powershell") {
return "C:\\Windows\\System32\r\nC:\\Users\\boudr\\.local\\bin\r\n";
}
if (command === "where.exe") {
return "C:\\Users\\boudr\\.local\\bin\\claude.exe\r\n";
}
throw new Error(`unexpected command ${command}`);
}) as any,
);
expect(findExecutable("claude", findExecutableDependencies)).toBe(
"C:\\Users\\boudr\\.local\\bin\\claude.exe",
);
const powershellCall = findExecutableDependencies.execFileSync.mock.calls[0];
expect(powershellCall?.[0]).toBe("powershell");
expect(powershellCall?.[1]).toContain("-NoProfile");
expect(powershellCall?.[1]).toContain("-NonInteractive");
expect(powershellCall?.[1]).toContain(
'$machine = [Environment]::GetEnvironmentVariable("Path", "Machine"); $user = [Environment]::GetEnvironmentVariable("Path", "User"); if ($machine) { Write-Output $machine }; if ($user) { Write-Output $user }',
);
const whereCall = findExecutableDependencies.execFileSync.mock.calls[1];
expect(whereCall?.[0]).toBe("where.exe");
expect(whereCall?.[1]).toEqual(["claude"]);
expect(whereCall?.[2]?.encoding).toBe("utf8");
const env = whereCall?.[2]?.env as Record<string, string | undefined>;
expect(env.PATH).toContain("C:\\Users\\boudr\\.local\\bin");
expect(env.Path).toContain("C:\\Users\\boudr\\.local\\bin");
});
test("on Windows, preserves the first where.exe match", () => {
findExecutableDependencies.platform = vi.fn(() => "win32");
process.env.Path = "C:\\Windows\\System32";
findExecutableDependencies.execFileSync.mockImplementation(
((command: string) => {
if (command === "powershell") {
return "C:\\Windows\\System32\r\nC:\\nvm4w\\nodejs\r\n";
}
if (command === "where.exe") {
return "C:\\nvm4w\\nodejs\\codex\r\nC:\\nvm4w\\nodejs\\codex.cmd\r\n";
}
throw new Error(`unexpected command ${command}`);
}) as any,
);
expect(findExecutable("codex", findExecutableDependencies)).toBe("C:\\nvm4w\\nodejs\\codex");
});
test("uses the last line from login-shell which output", () => {
findExecutableDependencies.shell = "/bin/zsh";
findExecutableDependencies.execSync.mockReturnValue(
"echo from profile\n/usr/local/bin/codex\n",
);
expect(findExecutable("codex", findExecutableDependencies)).toBe("/usr/local/bin/codex");
expect(findExecutableDependencies.execSync).toHaveBeenCalledOnce();
expect(findExecutableDependencies.execFileSync).not.toHaveBeenCalled();
});
test("warns and returns null when the final which line is not an absolute path", () => {
findExecutableDependencies.shell = "/bin/zsh";
findExecutableDependencies.execSync.mockReturnValue("profile noise\ncodex\n");
findExecutableDependencies.execFileSync.mockReturnValue("codex\n");
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
expect(findExecutable("codex", findExecutableDependencies)).toBeNull();
expect(warnSpy).toHaveBeenCalledTimes(2);
warnSpy.mockRestore();
});
test("returns direct paths when they exist", () => {
findExecutableDependencies.existsSync.mockReturnValue(true);
expect(findExecutable("/usr/local/bin/codex", findExecutableDependencies)).toBe(
"/usr/local/bin/codex",
);
expect(findExecutableDependencies.existsSync).toHaveBeenCalledWith("/usr/local/bin/codex");
});
});
describe("quoteWindowsCommand", () => {
const originalPlatform = process.platform;
function setPlatform(value: string) {
Object.defineProperty(process, "platform", { value, writable: true });
}
afterEach(() => {
setPlatform(originalPlatform);
});
test("quotes a Windows path with spaces", () => {
setPlatform("win32");
expect(quoteWindowsCommand("C:\\Program Files\\Anthropic\\claude.exe")).toBe(
'"C:\\Program Files\\Anthropic\\claude.exe"',
);
});
test("does not double-quote an already-quoted path", () => {
setPlatform("win32");
expect(quoteWindowsCommand('"C:\\Program Files\\Anthropic\\claude.exe"')).toBe(
'"C:\\Program Files\\Anthropic\\claude.exe"',
);
});
test("returns the command unchanged when there are no spaces", () => {
setPlatform("win32");
expect(quoteWindowsCommand("C:\\nvm4w\\nodejs\\codex")).toBe("C:\\nvm4w\\nodejs\\codex");
});
test("returns the command unchanged on non-Windows platforms", () => {
setPlatform("darwin");
expect(quoteWindowsCommand("/usr/local/bin/claude code")).toBe("/usr/local/bin/claude code");
});
});
describe("quoteWindowsArgument", () => {
const originalPlatform = process.platform;
function setPlatform(value: string) {
Object.defineProperty(process, "platform", { value, writable: true });
}
afterEach(() => {
setPlatform(originalPlatform);
});
test("quotes a Windows argument with spaces", () => {
setPlatform("win32");
expect(quoteWindowsArgument("C:\\Program Files\\Anthropic\\cli.js")).toBe(
'"C:\\Program Files\\Anthropic\\cli.js"',
);
});
test("does not double-quote an already-quoted argument", () => {
setPlatform("win32");
expect(quoteWindowsArgument('"C:\\Program Files\\Anthropic\\cli.js"')).toBe(
'"C:\\Program Files\\Anthropic\\cli.js"',
);
});
test("returns the argument unchanged when there are no spaces", () => {
setPlatform("win32");
expect(quoteWindowsArgument("--version")).toBe("--version");
});
test("returns the argument unchanged on non-Windows platforms", () => {
setPlatform("darwin");
expect(quoteWindowsArgument("/usr/local/bin/claude code")).toBe("/usr/local/bin/claude code");
});
});
+197
View File
@@ -0,0 +1,197 @@
import { execFileSync, execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { platform } from "node:os";
import path from "node:path";
import { shellEnvSync } from "shell-env";
export interface FindExecutableDependencies {
execSync: typeof execSync;
execFileSync: typeof execFileSync;
existsSync: typeof existsSync;
platform: typeof platform;
shell: string | undefined;
}
function resolveWindowsPathEntries(deps: FindExecutableDependencies): string[] {
try {
const output = deps.execFileSync(
"powershell",
[
"-NoProfile",
"-NonInteractive",
"-Command",
[
'$machine = [Environment]::GetEnvironmentVariable("Path", "Machine")',
'$user = [Environment]::GetEnvironmentVariable("Path", "User")',
"if ($machine) { Write-Output $machine }",
"if ($user) { Write-Output $user }",
].join("; "),
],
{ encoding: "utf8" },
);
return output
.split(/\r?\n/)
.flatMap((line) => line.split(";"))
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0);
} catch {
return [];
}
}
function resolveExecutableFromWhichOutput(
name: string,
output: string,
source: "login-shell" | "which",
): string | null {
const lines = output
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.length > 0);
const candidate = lines.at(-1);
if (!candidate) {
return null;
}
if (!path.isAbsolute(candidate)) {
console.warn(
`[findExecutable] Ignoring non-absolute ${source} output for '${name}': ${JSON.stringify(candidate)}`,
);
return null;
}
return candidate;
}
/**
* 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 we augment the daemon PATH with machine/user registry PATH values
* and return the first `where.exe` match. Launch-time execution decides whether
* the resolved path needs `cmd.exe` semantics (for example npm shims under
* nvm4w such as `C:\nvm4w\nodejs\codex`).
*/
export function findExecutable(
name: string,
dependencies?: FindExecutableDependencies,
): string | null {
const trimmed = name.trim();
if (!trimmed) {
return null;
}
const deps: FindExecutableDependencies = {
execSync,
execFileSync,
existsSync,
platform,
shell: process.env["SHELL"],
...dependencies,
};
if (trimmed.includes("/") || trimmed.includes("\\")) {
return deps.existsSync(trimmed) ? trimmed : null;
}
if (deps.platform() === "win32") {
try {
const inheritedPath = process.env["Path"] ?? process.env["PATH"] ?? "";
const resolvedPath = [
...inheritedPath.split(";"),
...resolveWindowsPathEntries(deps),
]
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0)
.filter((entry, index, entries) => entries.indexOf(entry) === index)
.join(";");
const env = {
...process.env,
PATH: resolvedPath,
Path: resolvedPath,
};
const out = deps.execFileSync("where.exe", [trimmed], { encoding: "utf8", env }).trim();
return (
out
.split(/\r?\n/)
.map((line) => line.trim())
.find((line) => line.length > 0) ?? null
);
} catch {
return null;
}
}
// Unix: try the user's login shell so rc-file PATH entries are visible.
const shell = deps.shell;
if (shell) {
try {
const out = deps
.execSync(`${shell} -lic "which ${trimmed}"`, {
encoding: "utf8",
timeout: 5000,
})
.trim();
const resolved = resolveExecutableFromWhichOutput(trimmed, out, "login-shell");
if (resolved) {
return resolved;
}
} catch {
// Login shell failed (broken rc, etc.) — fall through to plain which.
}
}
try {
return resolveExecutableFromWhichOutput(
trimmed,
deps.execFileSync("which", [trimmed], { encoding: "utf8" }).trim(),
"which",
);
} catch {
return null;
}
}
export function isCommandAvailable(command: string): boolean {
return findExecutable(command) !== null;
}
/**
* When spawning with `shell: true` on Windows, the command is passed to
* `cmd.exe /d /s /c "command args"`. The `/s` strips outer quotes, so a
* command path with spaces (e.g. `C:\Program Files\...`) is split at the
* space. Wrapping it in quotes produces the correct `"C:\Program Files\..." args`.
*/
export function quoteWindowsCommand(command: string): string {
if (process.platform !== "win32") return command;
if (!command.includes(" ")) return command;
if (command.startsWith('"') && command.endsWith('"')) return command;
return `"${command}"`;
}
/**
* `spawn(..., { shell: true })` on Windows also passes argv through `cmd.exe`.
* Any argument containing spaces must be quoted or it will be split before the
* child process sees it.
*/
export function quoteWindowsArgument(argument: string): string {
if (process.platform !== "win32") return argument;
if (!argument.includes(" ")) return argument;
if (argument.startsWith('"') && argument.endsWith('"')) return argument;
return `"${argument}"`;
}
let cachedShellEnv: Record<string, string> | null = null;
export function resolveShellEnv(): Record<string, string> {
if (cachedShellEnv) return cachedShellEnv;
try {
cachedShellEnv = shellEnvSync();
} catch {
cachedShellEnv = { ...process.env } as Record<string, string>;
}
return cachedShellEnv;
}