feat(session): Explicit session id naming (#5076)

This commit is contained in:
Armin Ronacher
2026-05-27 21:32:10 +02:00
committed by GitHub
parent 7c02a55632
commit 52dc08c1f7
7 changed files with 307 additions and 27 deletions

View File

@@ -37,7 +37,7 @@ import {
MissingSessionCwdError,
type SessionCwdIssue,
} from "./core/session-cwd.ts";
import { SessionManager } from "./core/session-manager.ts";
import { assertValidSessionId, SessionManager } from "./core/session-manager.ts";
import { SettingsManager } from "./core/settings-manager.ts";
import { printTimings, resetTimings, time } from "./core/timings.ts";
import { runMigrations, showDeprecationWarnings } from "./migrations.ts";
@@ -145,6 +145,16 @@ type ResolvedSession =
* Resolve a session argument to a file path.
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
*/
async function findLocalSessionByExactId(
sessionId: string,
cwd: string,
sessionDir?: string,
): Promise<{ type: "local"; path: string } | undefined> {
const localSessions = await SessionManager.list(cwd, sessionDir);
const localMatch = localSessions.find((s) => s.id === sessionId);
return localMatch ? { type: "local", path: localMatch.path } : undefined;
}
async function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {
// If it looks like a file path, resolve it before handing it to the session manager.
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
@@ -153,19 +163,20 @@ async function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?:
// Try to match as session ID in current project first
const localSessions = await SessionManager.list(cwd, sessionDir);
const localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));
const localMatch =
localSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));
if (localMatches.length >= 1) {
return { type: "local", path: localMatches[0].path };
if (localMatch) {
return { type: "local", path: localMatch.path };
}
// Try global search across all projects
const allSessions = await SessionManager.listAll();
const globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));
const globalMatch =
allSessions.find((s) => s.id === sessionArg) ?? allSessions.find((s) => s.id.startsWith(sessionArg));
if (globalMatches.length >= 1) {
const match = globalMatches[0];
return { type: "global", path: match.path, cwd: match.cwd };
if (globalMatch) {
return { type: "global", path: globalMatch.path, cwd: globalMatch.cwd };
}
// Not found anywhere
@@ -202,9 +213,33 @@ function validateForkFlags(parsed: Args): void {
}
}
function forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {
function validateSessionIdFlags(parsed: Args): void {
if (parsed.sessionId === undefined) return;
const conflictingFlags = [
parsed.session ? "--session" : undefined,
parsed.continue ? "--continue" : undefined,
parsed.resume ? "--resume" : undefined,
parsed.noSession ? "--no-session" : undefined,
].filter((flag): flag is string => flag !== undefined);
if (conflictingFlags.length > 0) {
console.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(", ")}`));
process.exit(1);
}
try {
return SessionManager.forkFrom(sourcePath, cwd, sessionDir);
assertValidSessionId(parsed.sessionId);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.error(chalk.red(`Error: ${message}`));
process.exit(1);
}
}
function forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string, sessionId?: string): SessionManager {
try {
return SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.error(chalk.red(`Error: ${message}`));
@@ -218,18 +253,26 @@ async function createSessionManager(
sessionDir: string | undefined,
settingsManager: SettingsManager,
): Promise<SessionManager> {
if (parsed.noSession) {
return SessionManager.inMemory();
if (parsed.noSession || parsed.help || parsed.listModels !== undefined) {
return SessionManager.inMemory(cwd);
}
if (parsed.fork) {
if (parsed.sessionId) {
const existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
if (existingTarget) {
console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
process.exit(1);
}
}
const resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);
switch (resolved.type) {
case "path":
case "local":
case "global":
return forkSessionOrExit(resolved.path, cwd, sessionDir);
return forkSessionOrExit(resolved.path, cwd, sessionDir, parsed.sessionId);
case "not_found":
console.error(chalk.red(`No session found matching '${resolved.arg}'`));
@@ -282,7 +325,14 @@ async function createSessionManager(
return SessionManager.continueRecent(cwd, sessionDir);
}
return SessionManager.create(cwd, sessionDir);
if (parsed.sessionId) {
const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
if (existingSession) {
return SessionManager.open(existingSession.path, sessionDir);
}
}
return SessionManager.create(cwd, sessionDir, { id: parsed.sessionId });
}
function buildSessionOptions(
@@ -483,6 +533,7 @@ export async function main(args: string[], options?: MainOptions) {
}
validateForkFlags(parsed);
validateSessionIdFlags(parsed);
// Run migrations (pass cwd for project-local migrations)
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());