diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md index bcbf05f64..5ebf949fe 100644 --- a/packages/coding-agent/docs/settings.md +++ b/packages/coding-agent/docs/settings.md @@ -127,6 +127,18 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's `npmCommand` is used for all npm package-manager operations, including `npm root -g`, installs, uninstalls, and `npm install` inside git packages. Use argv-style entries exactly as the process should be launched. +### Sessions + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| `sessionDir` | string | - | Directory where session files are stored. Accepts absolute or relative paths. | + +```json +{ "sessionDir": ".pi/sessions" } +``` + +When multiple sources specify a session directory, `--session-dir` CLI flag takes precedence, then `sessionDir` in settings.json, then extension hooks. + ### Model Cycling | Setting | Type | Default | Description | diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index 78dce1477..b94fa99ba 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -94,6 +94,7 @@ export interface Settings { autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5) showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME markdown?: MarkdownSettings; + sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag) } /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */ @@ -530,6 +531,10 @@ export class SettingsManager { this.save(); } + getSessionDir(): string | undefined { + return this.settings.sessionDir; + } + getDefaultProvider(): string | undefined { return this.settings.defaultProvider; } diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index cb39b3241..067d33523 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -439,16 +439,15 @@ async function createSessionManager( parsed: Args, cwd: string, extensions: LoadExtensionsResult, + settingsManager: SettingsManager, ): Promise { if (parsed.noSession) { return SessionManager.inMemory(); } - // CLI flag takes precedence, otherwise ask extensions for custom session directory - let effectiveSessionDir = parsed.sessionDir; - if (!effectiveSessionDir) { - effectiveSessionDir = await callSessionDirectoryHook(extensions, cwd); - } + // Priority: CLI flag > settings.json > extension hook + const effectiveSessionDir = + parsed.sessionDir ?? settingsManager.getSessionDir() ?? (await callSessionDirectoryHook(extensions, cwd)); if (parsed.fork) { const resolved = await resolveSessionPath(parsed.fork, cwd, effectiveSessionDir); @@ -789,13 +788,16 @@ export async function main(args: string[]) { time("resolveModelScope"); // Create session manager based on CLI flags - let sessionManager = await createSessionManager(parsed, cwd, extensionsResult); + let sessionManager = await createSessionManager(parsed, cwd, extensionsResult, time("createSessionManager"); // Handle --resume: show session picker if (parsed.resume) { // Compute effective session dir for resume (same logic as createSessionManager) - const effectiveSessionDir = parsed.sessionDir || (await callSessionDirectoryHook(extensionsResult, cwd)); + const effectiveSessionDir = + parsed.sessionDir ?? + settingsManager.getSessionDir() ?? + (await callSessionDirectoryHook(extensionsResult, cwd)); const selectedPath = await selectSession( (onProgress) => SessionManager.list(cwd, effectiveSessionDir, onProgress), diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 263dac6e7..029ee448d 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -288,4 +288,25 @@ describe("SettingsManager", () => { expect(savedSettings.theme).toBe("light"); }); }); + + describe("getSessionDir", () => { + it("should return undefined when not set", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ theme: "dark" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getSessionDir()).toBeUndefined(); + }); + + it("should return global sessionDir", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ sessionDir: "/tmp/sessions" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getSessionDir()).toBe("/tmp/sessions"); + }); + + it("should return project sessionDir, overriding global", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ sessionDir: "/global/sessions" })); + writeFileSync(join(projectDir, ".pi", "settings.json"), JSON.stringify({ sessionDir: "./sessions" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getSessionDir()).toBe("./sessions"); + }); + }); });