From 41dd3b11b7db4d492e933454663cdbe019543b4c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 13 Dec 2025 20:37:46 +0000 Subject: [PATCH] fix: harden pi package resolution --- src/agents/pi-path.ts | 77 ++++++++++++++++++++++++++++++++++--------- src/browser/chrome.ts | 3 +- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/agents/pi-path.ts b/src/agents/pi-path.ts index 5a003b55c..fd6a38b9b 100644 --- a/src/agents/pi-path.ts +++ b/src/agents/pi-path.ts @@ -1,26 +1,73 @@ import fs from "node:fs"; -import { createRequire } from "node:module"; import path from "node:path"; +import { fileURLToPath } from "node:url"; // Resolve the bundled pi/tau binary path from the installed dependency. export function resolveBundledPiBinary(): string | null { + const candidatePkgDirs: string[] = []; + + // Preferred: ESM resolution to the package entry, then walk up to package.json. try { - const require = createRequire(import.meta.url); - const pkgPath = require.resolve( - "@mariozechner/pi-coding-agent/package.json", - ); - const pkgDir = path.dirname(pkgPath); - // Prefer compiled binary if present, else fall back to dist/cli.js (has shebang). - const binCandidates = [ - path.join(pkgDir, "dist", "pi"), - path.join(pkgDir, "dist", "cli.js"), - path.join(pkgDir, "bin", "tau-dev.mjs"), - ]; - for (const candidate of binCandidates) { - if (fs.existsSync(candidate)) return candidate; + const resolved = (import.meta as { resolve?: (s: string) => string }) + .resolve; + const entryUrl = resolved?.("@mariozechner/pi-coding-agent"); + if (typeof entryUrl === "string" && entryUrl.startsWith("file:")) { + const entryPath = fileURLToPath(entryUrl); + let dir = path.dirname(entryPath); + for (let i = 0; i < 12; i += 1) { + const pkgJson = path.join(dir, "package.json"); + if (fs.existsSync(pkgJson)) { + candidatePkgDirs.push(dir); + break; + } + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } } } catch { - // Dependency missing or resolution failed. + // ignore; we'll try filesystem fallbacks below + } + + // Fallback: walk up from this module's directory to find node_modules. + try { + let dir = path.dirname(fileURLToPath(import.meta.url)); + for (let i = 0; i < 12; i += 1) { + candidatePkgDirs.push( + path.join(dir, "node_modules", "@mariozechner", "pi-coding-agent"), + ); + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } + } catch { + // ignore + } + + // Fallback: assume CWD is project root. + candidatePkgDirs.push( + path.resolve( + process.cwd(), + "node_modules", + "@mariozechner", + "pi-coding-agent", + ), + ); + + for (const pkgDir of candidatePkgDirs) { + try { + if (!fs.existsSync(pkgDir)) continue; + const binCandidates = [ + path.join(pkgDir, "dist", "pi"), + path.join(pkgDir, "dist", "cli.js"), + path.join(pkgDir, "bin", "tau-dev.mjs"), + ]; + for (const candidate of binCandidates) { + if (fs.existsSync(candidate)) return candidate; + } + } catch { + // ignore this candidate + } } return null; } diff --git a/src/browser/chrome.ts b/src/browser/chrome.ts index 2e2b18aaa..8e0909211 100644 --- a/src/browser/chrome.ts +++ b/src/browser/chrome.ts @@ -97,7 +97,8 @@ function safeReadJson(filePath: string): Record | null { if (!exists(filePath)) return null; const raw = fs.readFileSync(filePath, "utf-8"); const parsed = JSON.parse(raw) as unknown; - if (typeof parsed !== "object" || parsed === null) return null; + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) + return null; return parsed as Record; } catch { return null;