feat(chat): delay session creation until first message

The app now opens to a new chat by default on startup
New chats no longer create a session until you send your first message
Fixed mobile and VSCode sessions handling for better consistency
This commit is contained in:
Bohdan Triapitsyn
2025-12-21 20:18:51 +02:00
parent 75ba954307
commit 7699aab123
12 changed files with 273 additions and 152 deletions
+1 -1
View File
@@ -721,7 +721,7 @@ export const useSessionStore = create<SessionStore>()(
set((state) => {
const filteredSessions = state.sessions.filter((session) => !deletedSet.has(session.id));
if (state.currentSessionId && deletedSet.has(state.currentSessionId)) {
nextCurrentId = filteredSessions.length > 0 ? filteredSessions[0].id : null;
nextCurrentId = null;
} else {
nextCurrentId = state.currentSessionId;
}
@@ -56,6 +56,13 @@ export const MEMORY_LIMITS = {
export const ACTIVE_SESSION_WINDOW = 180;
export type NewSessionDraftState = {
open: boolean;
directoryOverride: string | null;
parentID: string | null;
title?: string;
};
export interface SessionStore {
sessions: Session[];
@@ -98,10 +105,16 @@ export interface SessionStore {
pendingInputText: string | null;
newSessionDraft: NewSessionDraftState;
getSessionAgentEditMode: (sessionId: string, agentName: string | undefined, defaultMode?: EditPermissionMode) => EditPermissionMode;
toggleSessionAgentEditMode: (sessionId: string, agentName: string | undefined, defaultMode?: EditPermissionMode) => void;
setSessionAgentEditMode: (sessionId: string, agentName: string | undefined, mode: EditPermissionMode, defaultMode?: EditPermissionMode) => void;
loadSessions: () => Promise<void>;
openNewSessionDraft: (options?: { directoryOverride?: string | null; parentID?: string | null; title?: string }) => void;
closeNewSessionDraft: () => void;
createSession: (title?: string, directoryOverride?: string | null, parentID?: string | null) => Promise<Session | null>;
createSessionFromAssistantMessage: (sourceMessageId: string) => Promise<void>;
+113 -4
View File
@@ -95,6 +95,7 @@ export const useSessionStore = create<SessionStore>()(
sessionActivityPhase: new Map(),
userSummaryTitles: new Map(),
pendingInputText: null,
newSessionDraft: { open: true, directoryOverride: null, parentID: null },
getSessionAgentEditMode: (sessionId: string, agentName: string | undefined, defaultMode?: EditPermissionMode) => {
return useContextStore.getState().getSessionAgentEditMode(sessionId, agentName, defaultMode);
@@ -109,7 +110,46 @@ export const useSessionStore = create<SessionStore>()(
},
loadSessions: () => useSessionManagementStore.getState().loadSessions(),
openNewSessionDraft: (options) => {
set({
newSessionDraft: {
open: true,
directoryOverride: options?.directoryOverride ?? null,
parentID: options?.parentID ?? null,
title: options?.title,
},
currentSessionId: null,
error: null,
});
try {
const configState = useConfigStore.getState();
const visibleAgents = configState.getVisibleAgents();
const agentName =
configState.currentAgentName ||
visibleAgents.find((agent) => agent.name === 'build')?.name ||
visibleAgents[0]?.name;
if (agentName) {
configState.setAgent(agentName);
}
} catch {
// ignored
}
},
closeNewSessionDraft: () => {
const realCurrentSessionId = useSessionManagementStore.getState().currentSessionId;
set({
newSessionDraft: { open: false, directoryOverride: null, parentID: null, title: undefined },
currentSessionId: realCurrentSessionId,
});
},
createSession: async (title?: string, directoryOverride?: string | null, parentID?: string | null) => {
get().closeNewSessionDraft();
const result = await useSessionManagementStore.getState().createSession(title, directoryOverride, parentID);
if (result?.id) {
@@ -179,7 +219,11 @@ export const useSessionStore = create<SessionStore>()(
shareSession: (id: string) => useSessionManagementStore.getState().shareSession(id),
unshareSession: (id: string) => useSessionManagementStore.getState().unshareSession(id),
setCurrentSession: async (id: string | null) => {
const previousSessionId = get().currentSessionId;
if (id) {
get().closeNewSessionDraft();
}
const previousSessionId = useSessionManagementStore.getState().currentSessionId;
const sessionDirectory = resolveSessionDirectory(
useSessionManagementStore.getState().sessions,
@@ -224,7 +268,64 @@ export const useSessionStore = create<SessionStore>()(
get().evictLeastRecentlyUsed();
},
loadMessages: (sessionId: string) => useMessageStore.getState().loadMessages(sessionId),
sendMessage: (content: string, providerID: string, modelID: string, agent?: string, attachments?: AttachedFile[], agentMentionName?: string) => {
sendMessage: async (content: string, providerID: string, modelID: string, agent?: string, attachments?: AttachedFile[], agentMentionName?: string) => {
const draft = get().newSessionDraft;
if (draft?.open) {
const created = await useSessionManagementStore
.getState()
.createSession(draft.title, draft.directoryOverride ?? null, draft.parentID ?? null);
if (!created?.id) {
throw new Error('Failed to create session');
}
const configState = useConfigStore.getState();
const draftAgentName = configState.currentAgentName;
const draftProviderId = configState.currentProviderId;
const draftModelId = configState.currentModelId;
if (draftProviderId && draftModelId) {
try {
useContextStore.getState().saveSessionModelSelection(created.id, draftProviderId, draftModelId);
} catch {
// ignored
}
}
if (draftAgentName) {
try {
useContextStore.getState().saveSessionAgentSelection(created.id, draftAgentName);
} catch {
// ignored
}
if (draftProviderId && draftModelId) {
try {
useContextStore
.getState()
.saveAgentModelForSession(created.id, draftAgentName, draftProviderId, draftModelId);
} catch {
// ignored
}
}
}
try {
useSessionManagementStore
.getState()
.initializeNewOpenChamberSession(created.id, configState.agents);
} catch {
// ignored
}
get().closeNewSessionDraft();
return useMessageStore
.getState()
.sendMessage(content, providerID, modelID, agent, created.id, attachments, agentMentionName);
}
const currentSessionId = useSessionManagementStore.getState().currentSessionId;
return useMessageStore.getState().sendMessage(content, providerID, modelID, agent, currentSessionId || undefined, attachments, agentMentionName);
},
@@ -309,6 +410,10 @@ export const useSessionStore = create<SessionStore>()(
setSessionDirectory: (sessionId: string, directory: string | null) => useSessionManagementStore.getState().setSessionDirectory(sessionId, directory),
getWorktreeMetadata: (sessionId: string) => useSessionManagementStore.getState().getWorktreeMetadata(sessionId),
getContextUsage: (contextLimit: number, outputLimit: number) => {
if (get().newSessionDraft?.open) {
return null;
}
const currentSessionId = useSessionManagementStore.getState().currentSessionId;
if (!currentSessionId) return null;
const messages = useMessageStore.getState().messages;
@@ -420,9 +525,11 @@ useSessionManagementStore.subscribe((state, prevState) => {
return;
}
const draftOpen = useSessionStore.getState().newSessionDraft?.open;
useSessionStore.setState({
sessions: state.sessions,
currentSessionId: state.currentSessionId,
currentSessionId: draftOpen ? null : state.currentSessionId,
lastLoadedDirectory: state.lastLoadedDirectory,
isLoading: state.isLoading,
error: state.error,
@@ -535,9 +642,11 @@ usePermissionStore.subscribe((state, prevState) => {
});
});
const bootDraftOpen = useSessionStore.getState().newSessionDraft?.open;
useSessionStore.setState({
sessions: useSessionManagementStore.getState().sessions,
currentSessionId: useSessionManagementStore.getState().currentSessionId,
currentSessionId: bootDraftOpen ? null : useSessionManagementStore.getState().currentSessionId,
lastLoadedDirectory: useSessionManagementStore.getState().lastLoadedDirectory,
isLoading: useSessionManagementStore.getState().isLoading,
error: useSessionManagementStore.getState().error,