feat(chats): add managed projectless chat sessions

Create projectless chat sessions under a managed, date-scoped Chats directory and clean abandoned or deleted session folders.

Add Chats to sidebar state, startup cache, shared context, and Electron Mini Chat while keeping VS Code project-only. Resolve managed chat directories to one server-side memory owner and document the runtime contracts.
This commit is contained in:
Bohdan Triapitsyn
2026-08-21 12:12:40 +03:00
parent 0d70a631f6
commit 9e87d7fdb9
46 changed files with 677 additions and 136 deletions
+11 -7
View File
@@ -25,6 +25,7 @@ import {
worktreeMapsEqual,
} from '@/lib/worktrees/worktreeManager';
import type { WorktreeMetadata } from '@/types/worktree';
import { CHAT_DRAFT_PROJECT_ID } from '@/lib/chatDirectories';
const MINI_CHAT_PRESENCE_CHANNEL = 'openchamber:mini-chat-presence';
@@ -153,9 +154,9 @@ const MiniChatBootstrap: React.FC<{ config: MiniChatConfig }> = ({ config }) =>
const sessionId = typeof detail?.sessionId === 'string' ? detail.sessionId.trim() : '';
if (!sessionId) return;
if (useSessionUIStore.getState().currentSessionId === sessionId) return;
const directory = typeof detail?.directory === 'string' && detail.directory.trim().length > 0
? detail.directory.trim()
: (sessions.find((entry) => entry.id === sessionId) as { directory?: string | null } | undefined)?.directory ?? null;
const sessionDirectory = (sessions.find((entry) => entry.id === sessionId) as { directory?: string | null } | undefined)?.directory?.trim();
const directory = sessionDirectory
|| (typeof detail?.directory === 'string' && detail.directory.trim().length > 0 ? detail.directory.trim() : null);
void sync.ensureSessionRenderable(sessionId);
setCurrentSession(sessionId, directory);
sessionBootstrappedRef.current = true;
@@ -166,9 +167,11 @@ const MiniChatBootstrap: React.FC<{ config: MiniChatConfig }> = ({ config }) =>
React.useEffect(() => {
if (config.mode !== 'draft' || draftOpen || currentSessionId) return;
const hasProjectTarget = Boolean(config.projectId || config.directory);
openNewSessionDraft({
selectedProjectId: config.projectId,
directoryOverride: config.directory,
target: hasProjectTarget ? 'project' : 'chat',
selectedProjectId: hasProjectTarget ? config.projectId : CHAT_DRAFT_PROJECT_ID,
directoryOverride: hasProjectTarget ? config.directory : null,
preserveDirectoryOverride: Boolean(config.directory),
});
}, [config, currentSessionId, draftOpen, openNewSessionDraft]);
@@ -278,10 +281,11 @@ const MiniChatPresencePublisher: React.FC = () => {
const useSessionUnavailable = (config: MiniChatConfig): boolean => {
const sessions = useSessions();
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
const draftOpen = useSessionUIStore((state) => state.newSessionDraft.open);
const [timedOut, setTimedOut] = React.useState(false);
React.useEffect(() => {
if (config.mode !== 'session' || !config.sessionId || currentSessionId === config.sessionId) {
if (draftOpen || config.mode !== 'session' || !config.sessionId || currentSessionId) {
setTimedOut(false);
return;
}
@@ -291,7 +295,7 @@ const useSessionUnavailable = (config: MiniChatConfig): boolean => {
}
const timeout = window.setTimeout(() => setTimedOut(true), 5000);
return () => window.clearTimeout(timeout);
}, [config.mode, config.sessionId, currentSessionId, sessions]);
}, [config.mode, config.sessionId, currentSessionId, draftOpen, sessions]);
return timedOut;
};