fix(session): improve session ID resolution with global search and fork support (#785)

When using `--session <UUID>`, the session lookup now:

1. Searches locally first (current project's session directory)
2. Falls back to global search across all projects
3. If found in different project, prompts user to fork the session
4. If not found anywhere, shows clear error instead of silently creating
   a broken session with malformed path

Adds `SessionManager.forkFrom()` to create a forked session from another
project, preserving full conversation history with updated cwd.
This commit is contained in:
Rafał Krzyważnia 2026-01-16 20:18:10 +01:00 committed by GitHub
parent 923b9cb9ee
commit 48b4324155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 11 deletions

View file

@ -1219,6 +1219,56 @@ export class SessionManager {
return new SessionManager(cwd, "", undefined, false);
}
/**
* Fork a session from another project directory into the current project.
* Creates a new session in the target cwd with the full history from the source session.
* @param sourcePath Path to the source session file
* @param targetCwd Target working directory (where the new session will be stored)
* @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
*/
static forkFrom(sourcePath: string, targetCwd: string, sessionDir?: string): SessionManager {
const sourceEntries = loadEntriesFromFile(sourcePath);
if (sourceEntries.length === 0) {
throw new Error(`Cannot fork: source session file is empty or invalid: ${sourcePath}`);
}
const sourceHeader = sourceEntries.find((e) => e.type === "session") as SessionHeader | undefined;
if (!sourceHeader) {
throw new Error(`Cannot fork: source session has no header: ${sourcePath}`);
}
const dir = sessionDir ?? getDefaultSessionDir(targetCwd);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
// Create new session file with new ID but forked content
const newSessionId = randomUUID();
const timestamp = new Date().toISOString();
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
const newSessionFile = join(dir, `${fileTimestamp}_${newSessionId}.jsonl`);
// Write new header pointing to source as parent, with updated cwd
const newHeader: SessionHeader = {
type: "session",
version: CURRENT_SESSION_VERSION,
id: newSessionId,
timestamp,
cwd: targetCwd,
parentSession: sourcePath,
};
appendFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`);
// Copy all non-header entries from source
for (const entry of sourceEntries) {
if (entry.type !== "session") {
appendFileSync(newSessionFile, `${JSON.stringify(entry)}\n`);
}
}
return new SessionManager(targetCwd, dir, newSessionFile, true);
}
/**
* List all sessions for a directory.
* @param cwd Working directory (used to compute default session directory)