|
2 | 2 | * agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results. |
3 | 3 | */ |
4 | 4 |
|
| 5 | +import { mkdirSync } from "node:fs"; |
5 | 6 | import { homedir } from "node:os"; |
6 | 7 | import { basename, dirname, isAbsolute, resolve } from "node:path"; |
7 | 8 | import type { Model } from "@earendil-works/pi-ai"; |
@@ -206,6 +207,10 @@ export interface RunOptions { |
206 | 207 | thinkingLevel?: ThinkingLevel; |
207 | 208 | /** Override working directory (e.g. for worktree isolation). */ |
208 | 209 | cwd?: string; |
| 210 | + /** Explicit session JSONL file. Implies persistence and resumes/appends when it exists. */ |
| 211 | + sessionFile?: string; |
| 212 | + /** Base directory used to resolve a relative sessionFile. Defaults to the effective cwd. */ |
| 213 | + sessionFileCwd?: string; |
209 | 214 | /** |
210 | 215 | * Where .pi config is discovered (project extensions, skills, pi settings, |
211 | 216 | * agent memory). Default: same as the working directory. The manager sets |
@@ -288,11 +293,20 @@ function forwardAbortSignal(session: AgentSession, signal?: AbortSignal): () => |
288 | 293 | return () => signal.removeEventListener("abort", onAbort); |
289 | 294 | } |
290 | 295 |
|
| 296 | +function resolveConfiguredPath(path: string | undefined, cwd: string): string | undefined { |
| 297 | + if (!path) return undefined; |
| 298 | + if (path === "~") return homedir(); |
| 299 | + if (path.startsWith("~/")) return resolve(homedir(), path.slice(2)); |
| 300 | + if (isAbsolute(path)) return path; |
| 301 | + return resolve(cwd, path); |
| 302 | +} |
| 303 | + |
291 | 304 | function resolveConfiguredSessionDir(sessionDir: string | undefined, cwd: string): string | undefined { |
292 | | - if (!sessionDir) return undefined; |
293 | | - if (sessionDir === "~" || sessionDir.startsWith("~/")) return resolve(homedir(), sessionDir.slice(2)); |
294 | | - if (isAbsolute(sessionDir)) return sessionDir; |
295 | | - return resolve(cwd, sessionDir); |
| 305 | + return resolveConfiguredPath(sessionDir, cwd); |
| 306 | +} |
| 307 | + |
| 308 | +function resolveConfiguredSessionFile(sessionFile: string | undefined, cwd: string): string | undefined { |
| 309 | + return resolveConfiguredPath(sessionFile, cwd); |
296 | 310 | } |
297 | 311 |
|
298 | 312 | export async function runAgent( |
@@ -563,11 +577,19 @@ export async function runAgent( |
563 | 577 | }); |
564 | 578 |
|
565 | 579 | const settingsManager = SettingsManager.create(configCwd, agentDir); |
566 | | - const configuredSessionDir = resolveConfiguredSessionDir(agentConfig?.sessionDir, effectiveCwd); |
| 580 | + const sessionFileInput = options.sessionFile ?? agentConfig?.sessionFile; |
| 581 | + const sessionConfigCwd = sessionFileInput ? (options.sessionFileCwd ?? effectiveCwd) : effectiveCwd; |
| 582 | + const configuredSessionDir = resolveConfiguredSessionDir(agentConfig?.sessionDir, sessionConfigCwd); |
| 583 | + const configuredSessionFile = resolveConfiguredSessionFile(sessionFileInput, sessionConfigCwd); |
567 | 584 | const defaultSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR ?? settingsManager.getSessionDir?.(); |
568 | | - const sessionManager = agentConfig?.persistSession |
569 | | - ? SessionManager.create(effectiveCwd, configuredSessionDir ?? defaultSessionDir) |
570 | | - : SessionManager.inMemory(effectiveCwd); |
| 585 | + if (configuredSessionFile) { |
| 586 | + mkdirSync(dirname(configuredSessionFile), { recursive: true }); |
| 587 | + } |
| 588 | + const sessionManager = configuredSessionFile |
| 589 | + ? SessionManager.open(configuredSessionFile, configuredSessionDir ?? dirname(configuredSessionFile), effectiveCwd) |
| 590 | + : agentConfig?.persistSession |
| 591 | + ? SessionManager.create(effectiveCwd, configuredSessionDir ?? defaultSessionDir) |
| 592 | + : SessionManager.inMemory(effectiveCwd); |
571 | 593 |
|
572 | 594 | const sessionOpts: Parameters<typeof createAgentSession>[0] = { |
573 | 595 | cwd: effectiveCwd, |
|
0 commit comments