Files
pi-agent-integrated/pi-web/lib/app-runtime.ts
T

124 lines
3.8 KiB
TypeScript

import { existsSync, realpathSync } from "fs";
import { isAbsolute, relative, resolve, sep } from "path";
import {
SettingsManager,
type ResourceDiagnostic,
type Skill,
} from "@earendil-works/pi-coding-agent";
export const MANAGED_RUNTIME_ENV = "PI_AGENT_MANAGED_RUNTIME";
export interface ManagedRuntimePaths {
appRoot: string;
dataDir: string;
agentDir: string;
resourcesDir: string;
skillsHomeDir: string;
stateDir: string;
managedSkillRoots: string[];
}
export interface AppResourceLoaderOptions {
additionalExtensionPaths?: string[];
additionalSkillPaths?: string[];
additionalPromptTemplatePaths?: string[];
additionalThemePaths?: string[];
skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {
skills: Skill[];
diagnostics: ResourceDiagnostic[];
};
}
export function isManagedRuntime(): boolean {
return process.env[MANAGED_RUNTIME_ENV] === "1";
}
function requiredPath(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required in managed runtime mode`);
return resolve(value);
}
export function getManagedRuntimePaths(): ManagedRuntimePaths {
const appRoot = requiredPath("PI_AGENT_APP_ROOT");
const dataDir = requiredPath("PI_AGENT_DATA_DIR");
const agentDir = requiredPath("PI_CODING_AGENT_DIR");
const resourcesDir = requiredPath("PI_AGENT_RESOURCES_DIR");
const skillsHomeDir = requiredPath("PI_AGENT_SKILLS_HOME");
const stateDir = resolve(process.env.XDG_STATE_HOME ?? `${dataDir}/state`);
return {
appRoot,
dataDir,
agentDir,
resourcesDir,
skillsHomeDir,
stateDir,
managedSkillRoots: [
resolve(resourcesDir, "skills"),
resolve(agentDir, "skills"),
resolve(skillsHomeDir, ".agents", "skills"),
resolve(skillsHomeDir, ".pi", "agent", "skills"),
],
};
}
function canonicalPath(path: string): string {
const resolved = resolve(path);
if (!existsSync(resolved)) return resolved;
try {
return realpathSync.native(resolved);
} catch {
return resolved;
}
}
export function isManagedPath(path: string): boolean {
if (!isManagedRuntime()) return true;
const candidate = canonicalPath(path);
const { appRoot, dataDir, resourcesDir } = getManagedRuntimePaths();
return [appRoot, dataDir, resourcesDir].some((root) => {
const rel = relative(canonicalPath(root), candidate);
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
});
}
export function createAppSettingsManager(
cwd: string,
agentDir: string,
projectTrusted = true,
): SettingsManager {
return SettingsManager.create(cwd, agentDir, {
projectTrusted: isManagedRuntime() ? false : projectTrusted,
});
}
export function getAppResourceLoaderOptions(): AppResourceLoaderOptions {
if (!isManagedRuntime()) return {};
const { resourcesDir, managedSkillRoots } = getManagedRuntimePaths();
return {
additionalExtensionPaths: [resolve(resourcesDir, "extensions")],
additionalSkillPaths: managedSkillRoots,
additionalPromptTemplatePaths: [resolve(resourcesDir, "prompts")],
additionalThemePaths: [resolve(resourcesDir, "themes")],
skillsOverride: ({ skills, diagnostics }) => ({
skills: skills.filter((skill) => isManagedPath(skill.filePath)),
diagnostics: diagnostics.filter((diagnostic) => !diagnostic.path || isManagedPath(diagnostic.path)),
}),
};
}
export function getSkillsCliEnvironment(): NodeJS.ProcessEnv {
if (!isManagedRuntime()) return { ...process.env, FORCE_COLOR: "0" };
const { dataDir, skillsHomeDir, stateDir } = getManagedRuntimePaths();
return {
...process.env,
HOME: skillsHomeDir,
USERPROFILE: skillsHomeDir,
XDG_STATE_HOME: stateDir,
npm_config_cache: resolve(dataDir, "cache", "npm"),
FORCE_COLOR: "0",
};
}