mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
fix(coding-agent): move temporary extension cache (#5345)
This commit is contained in:
parent
135fb545f9
commit
ea3465a8e3
4 changed files with 60 additions and 13 deletions
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fixed temporary extension package installs to use a private `~/.pi/agent/tmp/extensions` directory with `0700` permissions instead of `os.tmpdir()/pi-extensions`.
|
||||
- Fixed git package source handling to reject unsafe host/path components and keep managed clone paths inside install roots.
|
||||
- Fixed stored XSS in HTML session exports by sanitizing Markdown link and image URLs with a scheme allow-list after stripping control characters.
|
||||
- Fixed SDK embedding in bundled Node apps failing with `ENOENT` when `package.json` is not present next to the bundle entrypoint. The package metadata reader now gracefully handles missing `package.json` by using defaults, enabling `createAgentSession()` without requiring package-adjacent files at runtime ([#5226](https://github.com/earendil-works/pi/issues/5226)).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ChildProcess, ChildProcessByStdio } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
function getEnv(): NodeJS.ProcessEnv {
|
||||
if (process.platform !== "linux" || Object.keys(process.env).length > 0) {
|
||||
|
|
@ -206,6 +206,13 @@ function getHomeDir(): string {
|
|||
return process.env.HOME || homedir();
|
||||
}
|
||||
|
||||
export function getExtensionTempFolder(agentDir: string): string {
|
||||
const tempFolder = join(agentDir, "tmp", "extensions");
|
||||
mkdirSync(tempFolder, { recursive: true, mode: 0o700 });
|
||||
chmodSync(tempFolder, 0o700);
|
||||
return tempFolder;
|
||||
}
|
||||
|
||||
function prefixIgnorePattern(line: string, prefix: string): string | null {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return null;
|
||||
|
|
@ -1970,7 +1977,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
}
|
||||
|
||||
private getTemporaryDir(prefix: string, suffix?: string): string {
|
||||
const root = this.resolveManagedPath(join(tmpdir(), "pi-extensions"), prefix);
|
||||
const root = this.resolveManagedPath(getExtensionTempFolder(this.agentDir), prefix);
|
||||
const hash = createHash("sha256")
|
||||
.update(`${prefix}-${suffix ?? ""}`)
|
||||
.digest("hex")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -51,6 +50,20 @@ function getFileContent(repoDir: string, filename: string): string {
|
|||
return readFileSync(join(repoDir, filename), "utf-8");
|
||||
}
|
||||
|
||||
type GitSourceForTest = {
|
||||
type: "git";
|
||||
repo: string;
|
||||
host: string;
|
||||
path: string;
|
||||
pinned: boolean;
|
||||
ref?: string;
|
||||
};
|
||||
|
||||
interface PackageManagerPathInternals {
|
||||
parseSource(source: string): GitSourceForTest;
|
||||
getGitInstallPath(source: GitSourceForTest, scope: "temporary"): string;
|
||||
}
|
||||
|
||||
describe("DefaultPackageManager git update", () => {
|
||||
let tempDir: string;
|
||||
let remoteDir: string; // Simulates the "remote" repository
|
||||
|
|
@ -376,10 +389,8 @@ describe("DefaultPackageManager git update", () => {
|
|||
|
||||
describe("temporary git sources", () => {
|
||||
it("should refresh cached temporary git sources when resolving", async () => {
|
||||
const gitHost = "github.com";
|
||||
const gitPath = "test/extension";
|
||||
const hash = createHash("sha256").update(`git-${gitHost}-${gitPath}`).digest("hex").slice(0, 8);
|
||||
const cachedDir = join(tmpdir(), "pi-extensions", `git-${gitHost}`, hash, gitPath);
|
||||
const managerWithPaths = packageManager as unknown as PackageManagerPathInternals;
|
||||
const cachedDir = managerWithPaths.getGitInstallPath(managerWithPaths.parseSource(gitSource), "temporary");
|
||||
const extensionFile = join(cachedDir, "pi-extensions", "session-breakdown.ts");
|
||||
|
||||
rmSync(cachedDir, { recursive: true, force: true });
|
||||
|
|
@ -423,10 +434,8 @@ describe("DefaultPackageManager git update", () => {
|
|||
});
|
||||
|
||||
it("should not refresh pinned temporary git sources", async () => {
|
||||
const gitHost = "github.com";
|
||||
const gitPath = "test/extension";
|
||||
const hash = createHash("sha256").update(`git-${gitHost}-${gitPath}`).digest("hex").slice(0, 8);
|
||||
const cachedDir = join(tmpdir(), "pi-extensions", `git-${gitHost}`, hash, gitPath);
|
||||
const managerWithPaths = packageManager as unknown as PackageManagerPathInternals;
|
||||
const cachedDir = managerWithPaths.getGitInstallPath(managerWithPaths.parseSource(gitSource), "temporary");
|
||||
const extensionFile = join(cachedDir, "pi-extensions", "session-breakdown.ts");
|
||||
|
||||
rmSync(cachedDir, { recursive: true, force: true });
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { EventEmitter } from "node:events";
|
||||
import { mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, relative } from "node:path";
|
||||
import { PassThrough } from "node:stream";
|
||||
|
|
@ -33,6 +33,16 @@ interface PackageManagerInternals {
|
|||
options?: { cwd?: string; timeoutMs?: number; env?: Record<string, string> },
|
||||
): Promise<string>;
|
||||
getLocalGitUpdateTarget(installedPath: string): Promise<{ ref: string; head: string; fetchArgs: string[] }>;
|
||||
parseSource(
|
||||
source: string,
|
||||
):
|
||||
| { type: "npm"; spec: string; name: string; pinned: boolean }
|
||||
| { type: "git"; repo: string; host: string; path: string; pinned: boolean; ref?: string }
|
||||
| { type: "local"; path: string };
|
||||
getNpmInstallPath(
|
||||
source: { type: "npm"; spec: string; name: string; pinned: boolean },
|
||||
scope: "user" | "project" | "temporary",
|
||||
): string;
|
||||
getGitInstallPath(
|
||||
source: { type: "git"; repo: string; host: string; path: string; pinned: boolean; ref?: string },
|
||||
scope: "user" | "project" | "temporary",
|
||||
|
|
@ -1161,6 +1171,26 @@ Content`,
|
|||
});
|
||||
});
|
||||
|
||||
describe("temporary install paths", () => {
|
||||
it("should place temporary npm packages under the agent temp extension folder", () => {
|
||||
const managerWithInternals = packageManager as unknown as PackageManagerInternals;
|
||||
const source = managerWithInternals.parseSource("npm:left-pad");
|
||||
if (source.type !== "npm") {
|
||||
throw new Error("Expected npm source");
|
||||
}
|
||||
|
||||
const installPath = managerWithInternals.getNpmInstallPath(source, "temporary");
|
||||
const tempRoot = join(agentDir, "tmp", "extensions");
|
||||
|
||||
expect(pathEndsWith(installPath, "node_modules/left-pad")).toBe(true);
|
||||
expect(relative(tempRoot, installPath).startsWith("..")).toBe(false);
|
||||
expect(installPath.startsWith(join(tmpdir(), "pi-extensions"))).toBe(false);
|
||||
if (process.platform !== "win32") {
|
||||
expect(statSync(tempRoot).mode & 0o777).toBe(0o700);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("settings source normalization", () => {
|
||||
it("should store global local packages relative to agent settings base", () => {
|
||||
const pkgDir = join(tempDir, "packages", "local-global-pkg");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue