feat: generalize agent runtime config

This commit is contained in:
Federico Jaramillo Martinez
2026-06-28 16:36:40 +02:00
parent 84a485d62e
commit 75e2377756
22 changed files with 289 additions and 155 deletions
+40 -1
View File
@@ -15,7 +15,7 @@ import type { SessionProxyDaemon } from "./sessiond/sessionProxyRoutes.js";
import { PI_WEB_CAPABILITIES } from "../shared/capabilities.js";
import { machineScopedPluginId } from "../shared/machinePluginIds.js";
import { MAX_IMAGE_PREVIEW_BYTES } from "../shared/workspaceFiles.js";
import type { PiWebConfigResponse, PiWebConfigValues } from "../shared/apiTypes.js";
import type { PiWebConfigResponse, PiWebConfigValues, PiWebStatusResponse } from "../shared/apiTypes.js";
import type { Project, Workspace } from "./types.js";
let app: FastifyInstance;
@@ -556,6 +556,35 @@ describe("buildApp", () => {
]);
});
it("uses the latest configured agent dir for PI WEB status after config writes", async () => {
const originalSkipVersionCheck = process.env["PI_WEB_SKIP_VERSION_CHECK"];
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
try {
const initialAgentDir = join(tempDir, "initial-agent");
const updatedAgentDir = join(tempDir, "updated-agent");
piWebConfig = { agent: { command: "pi", dir: initialAgentDir } };
await mkdir(initialAgentDir, { recursive: true });
await installConfiguredPiWebPackage(updatedAgentDir);
const initialStatus = await app.inject({ method: "GET", url: "/api/pi-web/status" });
expect(initialStatus.statusCode).toBe(200);
const updateResponse = await app.inject({
method: "PUT",
url: "/api/config",
payload: { config: { agent: { command: "pi", dir: updatedAgentDir } } },
});
expect(updateResponse.statusCode).toBe(200);
const refreshedStatus = await app.inject({ method: "GET", url: "/api/pi-web/status" });
expect(refreshedStatus.statusCode).toBe(200);
expect(refreshedStatus.json<PiWebStatusResponse>().components.web.installation).toMatchObject({ kind: "pi-package", source: process.cwd(), scope: "user" });
} finally {
restoreEnv("PI_WEB_SKIP_VERSION_CHECK", originalSkipVersionCheck);
}
});
it("serves supported workspace images as previews", async () => {
const addResponse = await app.inject({
method: "POST",
@@ -920,6 +949,16 @@ function fakeSessionDaemon(): SessionProxyDaemon {
};
}
async function installConfiguredPiWebPackage(agentDir: string): Promise<void> {
await mkdir(agentDir, { recursive: true });
await writeFile(join(agentDir, "settings.json"), `${JSON.stringify({ packages: [process.cwd()] }, null, 2)}\n`, "utf8");
}
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) Reflect.deleteProperty(process.env, key);
else process.env[key] = value;
}
function fakeRemoteClient(overrides: Partial<MachineClient>): MachineClient {
return {
request: () => Promise.resolve({ statusCode: 200, headers: {}, body: Readable.from([]) }),
+35 -7
View File
@@ -20,9 +20,9 @@ import { registerTerminalProxyRoutes } from "./terminalProxyRoutes.js";
import { registerWorkspaceDeletionRoutes } from "./workspaces/workspaceDeletionRoutes.js";
import { createFilePiWebConfigService, registerConfigRoutes, type PiWebConfigService } from "./configRoutes.js";
import { PiWebPluginService } from "./piWebPluginService.js";
import { createPiWebStatusCache } from "./piWebStatusCache.js";
import { createPiWebStatusCache, type PiWebStatusCache } from "./piWebStatusCache.js";
import { getPiWebRuntime, getPiWebStatus, getPiWebVersionStatus } from "./piWebStatus.js";
import { effectiveAgentConfig, effectivePiWebConfig } from "../config.js";
import { effectiveAgentConfig, type EffectivePiWebAgentConfig } from "../config.js";
import { MachineService } from "./machines/machineService.js";
import { registerMachineRoutes } from "./machines/machineRoutes.js";
import { registerMachineProxyRoutes } from "./machines/machineProxyRoutes.js";
@@ -116,17 +116,42 @@ function registerLocalFileSuggestionRoutes(app: FastifyInstance, projects: Proje
});
}
async function readEffectiveConfig(config: Pick<PiWebConfigService, "read">) {
return (await config.read()).effectiveConfig;
}
async function readEffectiveAgentConfig(config: Pick<PiWebConfigService, "read">): Promise<EffectivePiWebAgentConfig> {
return effectiveAgentConfig(process.env, await readEffectiveConfig(config));
}
function invalidatePiWebStatusOnWrite(config: PiWebConfigService, statusCache: Pick<PiWebStatusCache, "invalidate">): PiWebConfigService {
return {
read: () => config.read(),
write: async (nextConfig) => {
const response = await config.write(nextConfig);
statusCache.invalidate();
return response;
},
};
}
export async function buildApp(deps: AppDependencies = {}): Promise<FastifyInstance> {
const app = Fastify({ logger: deps.logger ?? true, ...(deps.bodyLimit === undefined ? {} : { bodyLimit: deps.bodyLimit }) });
await app.register(fastifyWebsocket);
const projects = deps.projects ?? new ProjectService(new ProjectStore());
const workspaces = deps.workspaces ?? new WorkspaceService();
const agent = effectiveAgentConfig(process.env, effectivePiWebConfig().config);
const piWebPlugins = deps.piWebPlugins ?? new PiWebPluginService({ agentDir: agent.dir });
const configService = deps.config ?? createFilePiWebConfigService();
const readConfig = () => readEffectiveConfig(configService);
const readAgentConfig = () => readEffectiveAgentConfig(configService);
const piWebPlugins = deps.piWebPlugins ?? new PiWebPluginService({
configProvider: readConfig,
});
const sessionDaemon = deps.sessionDaemon ?? new SessionDaemonClient();
const piWebStatusCache = createPiWebStatusCache(() => getPiWebStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir }), {
const piWebStatusCache = createPiWebStatusCache(async () => {
const agent = await readAgentConfig();
return getPiWebStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir });
}, {
onError: (error) => { app.log.warn({ err: error }, "failed to refresh PI WEB status cache"); },
});
const machines = deps.machines ?? new MachineService(undefined, {
@@ -144,10 +169,13 @@ export async function buildApp(deps: AppDependencies = {}): Promise<FastifyInsta
});
app.get("/api/pi-web/status", async () => piWebStatusCache.get());
app.get("/api/pi-web/version", async () => getPiWebVersionStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir }));
app.get("/api/pi-web/version", async () => {
const agent = await readAgentConfig();
return getPiWebVersionStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir });
});
app.get("/api/pi-web/runtime", async () => getPiWebRuntime(sessionDaemon));
app.get("/api/plugins", async () => piWebPlugins.plugins());
registerConfigRoutes(app, configService);
registerConfigRoutes(app, invalidatePiWebStatusOnWrite(configService, piWebStatusCache));
registerMachineRoutes(app, machines);
registerMachinePluginProxyRoutes(app, machines);
+2 -2
View File
@@ -37,11 +37,11 @@ describe("config routes", () => {
const response = await app.inject({
method: "PUT",
url: "/api/config",
payload: { config: { host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "omp", dir: "~/.omp/agent" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads\\manual" }, maxUploadBytes: 1234 } },
payload: { config: { host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads\\manual" }, maxUploadBytes: 1234 } },
});
expect(response.statusCode).toBe(200);
expect(savedConfig).toEqual({ host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "omp", dir: "~/.omp/agent" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads/manual" }, maxUploadBytes: 1234 });
expect(savedConfig).toEqual({ host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads/manual" }, maxUploadBytes: 1234 });
expect(response.json<PiWebConfigResponse>().config).toEqual(savedConfig);
});
+6 -7
View File
@@ -1,5 +1,5 @@
import type { FastifyInstance } from "fastify";
import { effectiveAgentConfig, effectivePiWebConfig, hasAgentDirEnvOverride, hasAgentSessionDirEnvOverride, loadPiWebConfig, parseUploadsConfig, savePiWebConfig, type LoadOptions, type PiWebConfig } from "../config.js";
import { hasAgentDirEnvOverride, hasAgentSessionDirEnvOverride, loadPiWebConfig, parseUploadsConfig, resolveEffectivePiWebConfig, savePiWebConfig, type LoadOptions, type PiWebConfig } from "../config.js";
import type { PiWebConfigEnvOverrides, PiWebConfigResponse, PiWebConfigValues } from "../shared/apiTypes.js";
import { isPiWebPluginId } from "../shared/pluginIds.js";
@@ -20,14 +20,14 @@ export function createFilePiWebConfigService(options: LoadOptions = {}): PiWebCo
export function currentPiWebConfigResponse(options: LoadOptions = {}): PiWebConfigResponse {
const loaded = loadPiWebConfig(options);
const effective = effectivePiWebConfig(options);
const effective = resolveEffectivePiWebConfig(loaded, options);
const env = options.env ?? process.env;
return {
path: loaded.path,
exists: loaded.exists,
config: loaded.config,
effectiveConfig: effective.config,
envOverrides: piWebConfigEnvOverrides(env, loaded.config),
envOverrides: piWebConfigEnvOverrides(env),
};
}
@@ -167,8 +167,7 @@ function parsePluginsRequest(value: unknown): NonNullable<PiWebConfig["plugins"]
}));
}
function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv, config: PiWebConfig = {}): PiWebConfigEnvOverrides {
const agent = effectiveAgentConfig(env, config);
function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv): PiWebConfigEnvOverrides {
return {
host: isEnvSet(env["PI_WEB_HOST"]),
port: isEnvSet(env["PI_WEB_PORT"]) || isEnvSet(env["PORT"]),
@@ -176,8 +175,8 @@ function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv, config: PiWebConfig = {
spawnSessions: isEnvSet(env["PI_WEB_SPAWN_SESSIONS"]),
subsessions: isEnvSet(env["PI_WEB_SUBSESSIONS"]),
agentCommand: isEnvSet(env["PI_WEB_AGENT_COMMAND"]),
agentDir: hasAgentDirEnvOverride(env, agent.command),
agentSessionDir: hasAgentSessionDirEnvOverride(env, agent.command),
agentDir: hasAgentDirEnvOverride(env),
agentSessionDir: hasAgentSessionDirEnvOverride(env),
};
}
+21
View File
@@ -66,6 +66,27 @@ describe("PiWebPluginService", () => {
expect(manifest.plugins[0]?.module).toMatch(/^\/pi-web-plugins\/review\/dist\/review\.js\?v=\d+$/u);
});
it("uses the current config provider for Pi package plugin discovery", async () => {
const packageDir = join(tempDir, "pkg");
const initialAgentDir = join(tempDir, "initial-agent");
const updatedAgentDir = join(tempDir, "updated-agent");
let currentConfig = { agent: { dir: initialAgentDir } };
await writePlugin(packageDir, {
packageJson: { piWeb: { plugins: [{ id: "agent-package", module: "dist/plugin.js" }] } },
files: { "dist/plugin.js": "export default {};" },
});
await mkdir(initialAgentDir, { recursive: true });
await mkdir(updatedAgentDir, { recursive: true });
await writeFile(join(updatedAgentDir, "settings.json"), `${JSON.stringify({ packages: [packageDir] }, null, 2)}\n`, "utf8");
const service = new PiWebPluginService({ roots: [], cwd: tempDir, configProvider: () => currentConfig });
await expect(service.manifest()).resolves.toEqual({ plugins: [] });
currentConfig = { agent: { dir: updatedAgentDir } };
await expect(service.manifest()).resolves.toMatchObject({ plugins: [{ id: "agent-package", source: packageDir, scope: "user" }] });
});
it("discovers source checkout plugin packages without symlinks", async () => {
await mkdir(join(tempDir, "src", "server"), { recursive: true });
await writeFile(join(tempDir, "src", "server", "index.ts"), "export {};\n");
+46 -14
View File
@@ -2,8 +2,8 @@ import { existsSync } from "node:fs";
import { readdir, readFile, realpath, stat } from "node:fs/promises";
import { dirname, join, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
import { loadPiWebConfig, piWebDataDir, type PiWebConfig } from "../config.js";
import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
import { effectiveAgentConfig, loadPiWebConfig, piWebDataDir, type PiWebConfig } from "../config.js";
import type { PiWebPluginInfo, PiWebPluginsResponse, PiWebPluginScope } from "../shared/apiTypes.js";
import { isPiWebPluginId } from "../shared/pluginIds.js";
@@ -46,8 +46,9 @@ interface PiWebPluginServiceOptions {
roots?: LocalPluginRoot[];
cwd?: string;
agentDir?: string;
agentDirProvider?: () => string | Promise<string>;
packageProvider?: PiPackageProvider | false;
configProvider?: () => PiWebConfig;
configProvider?: () => PiWebConfig | Promise<PiWebConfig>;
}
interface LocalPluginRoot {
@@ -71,11 +72,12 @@ type ArraylessPluginRecord = Omit<PluginRecord, "source" | "scope">;
export class DefaultPiPackageProvider implements PiPackageProvider {
private readonly packageManager: DefaultPackageManager;
constructor(cwd = process.cwd(), agentDir = getAgentDir()) {
constructor(cwd = process.cwd(), agentDir?: string) {
const resolvedAgentDir = agentDir ?? defaultAgentDirForCwd(cwd);
this.packageManager = new DefaultPackageManager({
cwd,
agentDir,
settingsManager: SettingsManager.create(cwd, agentDir),
agentDir: resolvedAgentDir,
settingsManager: SettingsManager.create(cwd, resolvedAgentDir),
});
}
@@ -88,16 +90,32 @@ export class DefaultPiPackageProvider implements PiPackageProvider {
}
}
function defaultAgentDirForCwd(cwd: string): string {
return effectiveAgentConfig(process.env, loadPiWebConfig({ cwd }).config, cwd).dir;
}
export class PiWebPluginService {
private readonly cwd: string;
private readonly roots: LocalPluginRoot[];
private readonly packageProvider: PiPackageProvider | undefined;
private readonly configProvider: () => PiWebConfig;
private readonly agentDir: string | undefined;
private readonly agentDirProvider: (() => string | Promise<string>) | undefined;
private readonly packageProviderForAgentDir: ((agentDir: string) => PiPackageProvider) | undefined;
private readonly configProvider: () => PiWebConfig | Promise<PiWebConfig>;
constructor(options: PiWebPluginServiceOptions = {}) {
const cwd = options.cwd ?? process.cwd();
const agentDir = options.agentDir ?? getAgentDir();
this.cwd = cwd;
this.roots = options.roots ?? defaultPluginRoots(cwd);
this.packageProvider = options.packageProvider === false ? undefined : options.packageProvider ?? new DefaultPiPackageProvider(cwd, agentDir);
this.agentDir = options.agentDir;
this.agentDirProvider = options.agentDirProvider;
const packageProvider = options.packageProvider;
if (packageProvider === false) {
this.packageProviderForAgentDir = undefined;
} else if (packageProvider !== undefined) {
this.packageProviderForAgentDir = () => packageProvider;
} else {
this.packageProviderForAgentDir = (agentDir) => new DefaultPiPackageProvider(cwd, agentDir);
}
this.configProvider = options.configProvider ?? (() => loadPiWebConfig({ cwd }).config);
}
@@ -110,7 +128,8 @@ export class PiWebPluginService {
}
async plugins(): Promise<PiWebPluginsResponse> {
const [plugins, config] = await Promise.all([this.discoverPlugins(), Promise.resolve(this.configProvider())]);
const config = await this.configProvider();
const plugins = await this.discoverPlugins(config);
return { plugins: plugins.map((plugin) => this.pluginInfo(plugin, config)) };
}
@@ -143,15 +162,28 @@ export class PiWebPluginService {
};
}
private async discoverPlugins(): Promise<PluginRecord[]> {
private async discoverPlugins(config?: PiWebConfig): Promise<PluginRecord[]> {
const records = new Map<string, PluginRecord>();
for (const plugin of await this.discoverLocalPlugins()) addUnique(records, plugin);
if (this.packageProvider !== undefined) {
for (const plugin of await this.discoverPiPackagePlugins(this.packageProvider)) addUnique(records, plugin);
const packageProvider = await this.packageProvider(config);
if (packageProvider !== undefined) {
for (const plugin of await this.discoverPiPackagePlugins(packageProvider)) addUnique(records, plugin);
}
return [...records.values()].sort((left, right) => left.id.localeCompare(right.id));
}
private async packageProvider(config?: PiWebConfig): Promise<PiPackageProvider | undefined> {
if (this.packageProviderForAgentDir === undefined) return undefined;
return this.packageProviderForAgentDir(await this.currentAgentDir(config));
}
private async currentAgentDir(config?: PiWebConfig): Promise<string> {
if (this.agentDirProvider !== undefined) return await this.agentDirProvider();
if (this.agentDir !== undefined) return this.agentDir;
const currentConfig = config ?? await this.configProvider();
return effectiveAgentConfig(process.env, currentConfig, this.cwd).dir;
}
private async discoverLocalPlugins(): Promise<PluginRecord[]> {
const plugins: PluginRecord[] = [];
for (const root of this.roots) plugins.push(...await discoverLocalRoot(root));
+4 -4
View File
@@ -52,7 +52,7 @@ describe("PI WEB status", () => {
capabilities: [],
});
const status = await getPiWebVersionStatus(daemon, { agentCommand: "omp", agentDir });
const status = await getPiWebVersionStatus(daemon, { agentCommand: "alt-agent", agentDir });
expect(status.components.sessiond.installation).toMatchObject({ kind: "pi-package", source: process.cwd(), scope: "user" });
} finally {
@@ -85,12 +85,12 @@ describe("PI WEB status", () => {
{ kind: "pi-package", source: "npm:@jmfederico/pi-web", scope: "user", path: "/tmp/pi-web" },
"pi-web restart",
{
agentCommand: "/tmp/agent's/omp",
hasCommand: (command) => Promise.resolve(command === "/tmp/agent's/omp"),
agentCommand: "/tmp/agent's/alt-agent",
hasCommand: (command) => Promise.resolve(command === "/tmp/agent's/alt-agent"),
},
);
expect(updateCommand).toBe("'/tmp/agent'\\''s/omp' update 'npm:@jmfederico/pi-web' && pi-web restart");
expect(updateCommand).toBe("'/tmp/agent'\\''s/alt-agent' update 'npm:@jmfederico/pi-web' && pi-web restart");
});
it("suggests native systemd commands for local development services", async () => {
+4
View File
@@ -11,6 +11,7 @@ export interface PiWebStatusCacheOptions {
export interface PiWebStatusCache {
get(): Promise<PiWebStatusResponse>;
refresh(): Promise<PiWebStatusResponse>;
invalidate(): void;
}
export function createPiWebStatusCache(load: () => Promise<PiWebStatusResponse>, options: PiWebStatusCacheOptions = {}): PiWebStatusCache {
@@ -42,5 +43,8 @@ export function createPiWebStatusCache(load: () => Promise<PiWebStatusResponse>,
return refresh();
},
refresh,
invalidate(): void {
cached = undefined;
},
};
}
+2 -2
View File
@@ -44,9 +44,9 @@ describe("AuthService", () => {
const agentDir = await tempAgentDir();
const auth = new AuthService({ agentDir });
auth.saveApiKey("anthropic", "sk-omp");
auth.saveApiKey("anthropic", "sk-test");
await expect(readFile(join(agentDir, "auth.json"), "utf8")).resolves.toContain("sk-omp");
await expect(readFile(join(agentDir, "auth.json"), "utf8")).resolves.toContain("sk-test");
auth.dispose();
});
});
@@ -60,12 +60,12 @@ describe("SessionDirResolver", () => {
expect(resolver.resolve(cwd)).toMatchObject({ source: "env", sessionDir: envDir, usesConfiguredSessionDir: true });
});
it("uses OMP sessionDir environment overrides before settings", async () => {
const envDir = join(tempDir, "omp-env-sessions");
it("uses PI WEB sessionDir environment overrides before settings", async () => {
const envDir = join(tempDir, "pi-web-env-sessions");
await mkdir(agentDir, { recursive: true });
await writeFile(join(agentDir, "settings.json"), `${JSON.stringify({ sessionDir: join(tempDir, "settings-sessions") }, null, 2)}\n`, "utf8");
const resolver = new SessionDirResolver({ agentDir, env: { OMP_CODING_AGENT_SESSION_DIR: envDir }, sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"] });
const resolver = new SessionDirResolver({ agentDir, env: { PI_WEB_AGENT_SESSION_DIR: envDir } });
expect(resolver.resolve(cwd)).toMatchObject({ source: "env", sessionDir: envDir, usesConfiguredSessionDir: true });
});
@@ -92,14 +92,13 @@ describe("Pi session manager gateway", () => {
await expect(gateway.listAll()).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: "default-session", cwd }), expect.objectContaining({ id: "env-session", cwd })]));
});
it("includes command-specific env session directories in global listing", async () => {
for (const envKey of ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR"]) {
it("includes generic env session directories in global listing", async () => {
for (const envKey of ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"]) {
const envSessionDir = join(tempDir, `${envKey.toLowerCase()}-sessions`);
await writeSessionFile(envSessionDir, `${envKey.toLowerCase()}-session`, cwd);
const gateway = createPiSessionManagerGateway({
agentDir,
env: { [envKey]: envSessionDir },
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
});
if (gateway.listAll === undefined) throw new Error("Expected legacy listing support");
@@ -2,12 +2,11 @@ import type { Dirent } from "node:fs";
import { readdir } from "node:fs/promises";
import { homedir } from "node:os";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { getAgentDir, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
import { SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
import { agentSessionDirEnvKeys, effectiveAgentConfig } from "../../config.js";
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
import type { PiSessionListEntry, PiSessionManager, PiSessionManagerGateway } from "./piSessionService.js";
export const PI_SESSION_DIR_ENV = "PI_CODING_AGENT_SESSION_DIR";
type SessionDirSource = "env" | "settings" | "pi-default";
export interface SessionDirResolution {
@@ -28,9 +27,9 @@ export class SessionDirResolver {
private readonly sessionDirEnvKeys: readonly string[];
constructor(options: SessionDirResolverOptions = {}) {
this.agentDir = options.agentDir ?? getAgentDir();
this.agentDir = options.agentDir ?? effectiveAgentConfig().dir;
this.env = options.env ?? process.env;
this.sessionDirEnvKeys = options.sessionDirEnvKeys ?? [PI_SESSION_DIR_ENV];
this.sessionDirEnvKeys = options.sessionDirEnvKeys ?? agentSessionDirEnvKeys();
}
defaultSessionsRoot(): string {
@@ -131,11 +130,11 @@ function uniqueSessionsByPath(sessions: readonly PiSessionListEntry[]): PiSessio
return [...byPath.values()].sort((a, b) => b.modified.getTime() - a.modified.getTime());
}
export function defaultPiSessionsRoot(agentDir = getAgentDir()): string {
export function defaultPiSessionsRoot(agentDir = effectiveAgentConfig().dir): string {
return join(agentDir, "sessions");
}
export function defaultPiSessionDir(cwd: string, agentDir = getAgentDir()): string {
export function defaultPiSessionDir(cwd: string, agentDir = effectiveAgentConfig().dir): string {
return sessionDirInDefaultPiStore(defaultPiSessionsRoot(agentDir), cwd);
}
+2 -2
View File
@@ -7,7 +7,6 @@ import {
createAgentSessionServices,
createEditToolDefinition,
defineTool,
getAgentDir,
ModelRegistry,
SessionManager,
type CreateAgentSessionRuntimeFactory,
@@ -25,6 +24,7 @@ import { createModelRegistryForAgentDir, type AuthChange } from "./authService.j
import { fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
import { computeEditPreview, type EditPreviewResult } from "./editPreview.js";
import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
import { effectiveAgentConfig } from "../../config.js";
import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachmentService.js";
import { parsePromptAttachments } from "../../shared/promptAttachments.js";
import type { SavedPromptAttachment } from "../../shared/apiTypes.js";
@@ -338,7 +338,7 @@ export class PiSessionService implements SessionRouteService {
constructor(private readonly events: SessionEventHub, deps: PiSessionServiceDependencies = {}) {
this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
this.agentDir = deps.agentDir ?? getAgentDir();
this.agentDir = deps.agentDir ?? effectiveAgentConfig().dir;
this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({ agentDir: this.agentDir });
this.modelRegistry = deps.modelRegistry ?? createModelRegistryForAgentDir(this.agentDir);
this.spawnTargets = deps.spawnTargets;