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
+18
View File
@@ -35,6 +35,7 @@ import { getStaleRunningToolMessageID } from "./materialization"
import { normalizePath } from "@/lib/pathNormalization"
import { mergeMessages } from "./optimistic"
import { messagesBefore, messagesFrom } from "./message-ordering"
import { deleteChatDirectory } from "@/lib/chatDirectories"
const MESSAGE_REFETCH_LIMIT = 100
const SEND_CONFIRMATION_REFETCH_LIMIT = 30
@@ -919,6 +920,15 @@ function finalizeConfirmedSessionDeletion(
}
}
async function cleanupDeletedChatDirectory(directory: string | undefined, deleteDirectory: boolean): Promise<void> {
if (!directory || !deleteDirectory) return
try {
await deleteChatDirectory(directory)
} catch (error) {
console.warn("[session-actions] deleted chat directory cleanup failed", error)
}
}
export type DeleteSessionOptions = {
/**
* Runtime key the deletion is scoped to. Defaults to the active runtime when
@@ -947,6 +957,8 @@ export async function deleteSession(sessionId: string, options?: DeleteSessionOp
const expectedRuntimeKey = options?.expectedRuntimeKey ?? getRuntimeKey()
if (isStaleRuntime(expectedRuntimeKey)) return false
const sessionDirectory = getSessionDirectory(sessionId)
const sessionSnapshot = getGlobalSessionSnapshot(sessionId)
const deleteManagedDirectory = Boolean(sessionSnapshot && sessionSnapshot.parentID == null)
try {
await cleanupReviewMetadataBeforeDelete(sessionId, sessionDirectory, expectedRuntimeKey)
if (isStaleRuntime(expectedRuntimeKey)) return false
@@ -956,6 +968,7 @@ export async function deleteSession(sessionId: string, options?: DeleteSessionOp
throw new Error("session.delete failed: server did not confirm deletion")
}
finalizeConfirmedSessionDeletion(sessionId, sessionDirectory, expectedRuntimeKey)
await cleanupDeletedChatDirectory(sessionDirectory, deleteManagedDirectory)
return true
} catch (error) {
console.error("[session-actions] deleteSession failed", error)
@@ -965,6 +978,7 @@ export async function deleteSession(sessionId: string, options?: DeleteSessionOp
if ((error as { status?: number })?.status === 404) {
if (isStaleRuntime(expectedRuntimeKey)) return false
finalizeConfirmedSessionDeletion(sessionId, sessionDirectory, expectedRuntimeKey)
await cleanupDeletedChatDirectory(sessionDirectory, deleteManagedDirectory)
return true
}
return false
@@ -978,6 +992,8 @@ export async function deleteSessionInDirectory(
expectedRuntimeKey = getRuntimeKey(),
): Promise<boolean> {
if (isStaleRuntime(expectedRuntimeKey)) return false
const sessionSnapshot = getGlobalSessionSnapshot(sessionId)
const deleteManagedDirectory = Boolean(sessionSnapshot && sessionSnapshot.parentID == null)
try {
await cleanupReviewMetadataBeforeDelete(sessionId, directory, expectedRuntimeKey)
if (isStaleRuntime(expectedRuntimeKey)) return false
@@ -987,12 +1003,14 @@ export async function deleteSessionInDirectory(
throw new Error("session.delete failed: server did not confirm deletion")
}
finalizeConfirmedSessionDeletion(sessionId, directory, expectedRuntimeKey)
await cleanupDeletedChatDirectory(directory, deleteManagedDirectory)
return true
} catch (error) {
console.error("[session-actions] deleteSessionInDirectory failed", error)
if ((error as { status?: number })?.status === 404) {
if (isStaleRuntime(expectedRuntimeKey)) return false
finalizeConfirmedSessionDeletion(sessionId, directory, expectedRuntimeKey)
await cleanupDeletedChatDirectory(directory, deleteManagedDirectory)
return true
}
return false