feat(session): Explicit session id naming (#5076)
This commit is contained in:
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user