Merge pull request #13 from alexispurslane/blank-session-cleanup
Blank session cleanup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ release/
|
|||||||
.vite/
|
.vite/
|
||||||
.electron-vite/
|
.electron-vite/
|
||||||
out/
|
out/
|
||||||
|
.dir-locals.el
|
||||||
82
dev-docs/solidjs-llms.txt
Normal file
82
dev-docs/solidjs-llms.txt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# SolidJS Documentation
|
||||||
|
|
||||||
|
> Solid is a modern JavaScript framework for building user interfaces with fine-grained reactivity. It compiles JSX to real DOM elements and updates only what changes, delivering exceptional performance without a virtual DOM. Solid provides reactive primitives like signals, effects, and stores for predictable state management.
|
||||||
|
|
||||||
|
SolidJS is a declarative JavaScript framework that prioritizes performance and developer experience. Unlike frameworks that re-run components on every update, Solid components run once during initialization and set up a reactive system that precisely updates the DOM when dependencies change.
|
||||||
|
|
||||||
|
Key principles:
|
||||||
|
- Fine-grained reactivity: Updates only the specific DOM nodes that depend on changed data
|
||||||
|
- Compile-time optimization: JSX transforms into efficient DOM operations
|
||||||
|
- Unidirectional data flow: Props are read-only, promoting predictable state management
|
||||||
|
- Component lifecycle: Components run once, with reactive primitives handling updates
|
||||||
|
|
||||||
|
**Use your web fetch tool on any of the following links to understand the relevant concept**.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
- [Overview](https://docs.solidjs.com/): Framework introduction and key advantages
|
||||||
|
- [Quick Start](https://docs.solidjs.com/quick-start): Installation and project setup with create-solid
|
||||||
|
- [Interactive Tutorial](https://www.solidjs.com/tutorial/introduction_basics): Learn Solid basics through guided examples
|
||||||
|
- [Playground](https://playground.solidjs.com/): Experiment with Solid directly in your browser
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
- [Intro to Reactivity](https://docs.solidjs.com/concepts/intro-to-reactivity): Signals, subscribers, and reactive principles
|
||||||
|
- [Understanding JSX](https://docs.solidjs.com/concepts/understanding-jsx): How Solid uses JSX and key differences from HTML
|
||||||
|
- [Components Basics](https://docs.solidjs.com/concepts/components/basics): Component trees, lifecycles, and composition patterns
|
||||||
|
- [Signals](https://docs.solidjs.com/concepts/signals): Core reactive primitive for state management with getters/setters
|
||||||
|
- [Effects](https://docs.solidjs.com/concepts/effects): Side effects, dependency tracking, and lifecycle functions
|
||||||
|
- [Stores](https://docs.solidjs.com/concepts/stores): Complex state management with proxy-based reactivity
|
||||||
|
- [Context](https://docs.solidjs.com/concepts/context): Cross-component state sharing without prop drilling
|
||||||
|
|
||||||
|
## Component APIs
|
||||||
|
|
||||||
|
- [Props](https://docs.solidjs.com/concepts/components/props): Passing data and handlers to child components
|
||||||
|
- [Event Handlers](https://docs.solidjs.com/concepts/components/event-handlers): Managing user interactions
|
||||||
|
- [Class and Style](https://docs.solidjs.com/concepts/components/class-style): Dynamic styling approaches
|
||||||
|
- [Refs](https://docs.solidjs.com/concepts/refs): Accessing DOM elements directly
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
- [Conditional Rendering](https://docs.solidjs.com/concepts/control-flow/conditional-rendering): Show, Switch, and Match components
|
||||||
|
- [List Rendering](https://docs.solidjs.com/concepts/control-flow/list-rendering): For, Index, and keyed iteration
|
||||||
|
- [Dynamic](https://docs.solidjs.com/concepts/control-flow/dynamic): Dynamic component switching
|
||||||
|
- [Portal](https://docs.solidjs.com/concepts/control-flow/portal): Rendering outside component hierarchy
|
||||||
|
- [Error Boundary](https://docs.solidjs.com/concepts/control-flow/error-boundary): Graceful error handling
|
||||||
|
|
||||||
|
## Derived Values
|
||||||
|
|
||||||
|
- [Derived Signals](https://docs.solidjs.com/concepts/derived-values/derived-signals): Computed values from signals
|
||||||
|
- [Memos](https://docs.solidjs.com/concepts/derived-values/memos): Cached computed values for performance
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
- [Basic State Management](https://docs.solidjs.com/guides/state-management): One-way data flow and lifting state
|
||||||
|
- [Complex State Management](https://docs.solidjs.com/guides/complex-state-management): Stores for scalable applications
|
||||||
|
- [Fetching Data](https://docs.solidjs.com/guides/fetching-data): Async data with createResource
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
- [Routing & Navigation](https://docs.solidjs.com/guides/routing-and-navigation): @solidjs/router setup and usage
|
||||||
|
- [Dynamic Routes](https://docs.solidjs.com/guides/routing-and-navigation#dynamic-routes): Route parameters and validation
|
||||||
|
- [Nested Routes](https://docs.solidjs.com/guides/routing-and-navigation#nested-routes): Hierarchical route structures
|
||||||
|
- [Preload Functions](https://docs.solidjs.com/guides/routing-and-navigation#preload-functions): Parallel data fetching
|
||||||
|
|
||||||
|
## Advanced Topics
|
||||||
|
|
||||||
|
- [Fine-Grained Reactivity](https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity): Deep dive into reactive system
|
||||||
|
- [TypeScript](https://docs.solidjs.com/configuration/typescript): Type safety and configuration
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
- [Solid Router](https://docs.solidjs.com/solid-router/): File-system routing and data APIs
|
||||||
|
- [SolidStart](https://docs.solidjs.com/solid-start/): Full-stack meta-framework
|
||||||
|
- [Solid Meta](https://docs.solidjs.com/solid-meta/): Document head management
|
||||||
|
- [Templates](https://github.com/solidjs/templates): Starter templates for different setups
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- [Ecosystem Libraries](https://www.solidjs.com/ecosystem): Community packages and tools
|
||||||
|
- [API Reference](https://docs.solidjs.com/reference/): Complete API documentation
|
||||||
|
- [Testing](https://docs.solidjs.com/guides/testing): Testing strategies and utilities
|
||||||
|
- [Deployment](https://docs.solidjs.com/guides/deploying-your-app): Build and deployment options
|
||||||
@@ -18,6 +18,7 @@ const PreferencesSchema = z.object({
|
|||||||
toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
|
toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
|
||||||
diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
|
diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
|
||||||
showUsageMetrics: z.boolean().default(true),
|
showUsageMetrics: z.boolean().default(true),
|
||||||
|
autoCleanupBlankSessions: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
const RecentFolderSchema = z.object({
|
const RecentFolderSchema = z.object({
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const App: Component = () => {
|
|||||||
preferences,
|
preferences,
|
||||||
recordWorkspaceLaunch,
|
recordWorkspaceLaunch,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
setDiffViewMode,
|
setDiffViewMode,
|
||||||
setToolOutputExpansion,
|
setToolOutputExpansion,
|
||||||
@@ -206,6 +207,7 @@ const App: Component = () => {
|
|||||||
|
|
||||||
const { commands: paletteCommands, executeCommand } = useCommands({
|
const { commands: paletteCommands, executeCommand } = useCommands({
|
||||||
preferences,
|
preferences,
|
||||||
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
setDiffViewMode,
|
setDiffViewMode,
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ import { showAlertDialog } from "../../stores/alerts"
|
|||||||
import type { Instance } from "../../types/instance"
|
import type { Instance } from "../../types/instance"
|
||||||
import type { MessageRecord } from "../../stores/message-v2/types"
|
import type { MessageRecord } from "../../stores/message-v2/types"
|
||||||
import { messageStoreBus } from "../../stores/message-v2/bus"
|
import { messageStoreBus } from "../../stores/message-v2/bus"
|
||||||
|
import { cleanupBlankSessions } from "../../stores/session-state"
|
||||||
|
|
||||||
export interface UseCommandsOptions {
|
export interface UseCommandsOptions {
|
||||||
preferences: Accessor<Preferences>
|
preferences: Accessor<Preferences>
|
||||||
toggleShowThinkingBlocks: () => void
|
toggleShowThinkingBlocks: () => void
|
||||||
toggleUsageMetrics: () => void
|
toggleUsageMetrics: () => void
|
||||||
|
toggleAutoCleanupBlankSessions: () => void
|
||||||
setDiffViewMode: (mode: "split" | "unified") => void
|
setDiffViewMode: (mode: "split" | "unified") => void
|
||||||
setToolOutputExpansion: (mode: ExpansionPreference) => void
|
setToolOutputExpansion: (mode: ExpansionPreference) => void
|
||||||
setDiagnosticsExpansion: (mode: ExpansionPreference) => void
|
setDiagnosticsExpansion: (mode: ExpansionPreference) => void
|
||||||
@@ -142,6 +144,19 @@ export function useCommands(options: UseCommandsOptions) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
commandRegistry.register({
|
||||||
|
id: "cleanup-blank-sessions",
|
||||||
|
label: "Scrub Sessions",
|
||||||
|
description: "Remove empty sessions, subagent sessions that have completed their primary task, and extraneous forked sessions.",
|
||||||
|
category: "Session",
|
||||||
|
keywords: ["cleanup", "blank", "empty", "sessions", "remove", "delete", "scrub"],
|
||||||
|
action: async () => {
|
||||||
|
const instance = activeInstance()
|
||||||
|
if (!instance) return
|
||||||
|
cleanupBlankSessions(instance.id, undefined, true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
commandRegistry.register({
|
commandRegistry.register({
|
||||||
id: "switch-to-info",
|
id: "switch-to-info",
|
||||||
label: "Instance Info",
|
label: "Instance Info",
|
||||||
@@ -467,6 +482,18 @@ export function useCommands(options: UseCommandsOptions) {
|
|||||||
keywords: ["token", "usage", "cost", "stats"],
|
keywords: ["token", "usage", "cost", "stats"],
|
||||||
action: options.toggleUsageMetrics,
|
action: options.toggleUsageMetrics,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
commandRegistry.register({
|
||||||
|
id: "auto-cleanup-blank-sessions",
|
||||||
|
label: () => {
|
||||||
|
const enabled = options.preferences().autoCleanupBlankSessions
|
||||||
|
return `Auto-Cleanup Blank Sessions · ${enabled ? "Enabled" : "Disabled"}`
|
||||||
|
},
|
||||||
|
description: "Automatically clean up blank sessions when creating new ones",
|
||||||
|
category: "System",
|
||||||
|
keywords: ["auto", "cleanup", "blank", "sessions", "toggle"],
|
||||||
|
action: options.toggleAutoCleanupBlankSessions,
|
||||||
|
})
|
||||||
|
|
||||||
commandRegistry.register({
|
commandRegistry.register({
|
||||||
id: "help",
|
id: "help",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface Preferences {
|
|||||||
toolOutputExpansion: ExpansionPreference
|
toolOutputExpansion: ExpansionPreference
|
||||||
diagnosticsExpansion: ExpansionPreference
|
diagnosticsExpansion: ExpansionPreference
|
||||||
showUsageMetrics: boolean
|
showUsageMetrics: boolean
|
||||||
|
autoCleanupBlankSessions?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenCodeBinary {
|
export interface OpenCodeBinary {
|
||||||
@@ -64,6 +65,7 @@ const defaultPreferences: Preferences = {
|
|||||||
toolOutputExpansion: "expanded",
|
toolOutputExpansion: "expanded",
|
||||||
diagnosticsExpansion: "expanded",
|
diagnosticsExpansion: "expanded",
|
||||||
showUsageMetrics: true,
|
showUsageMetrics: true,
|
||||||
|
autoCleanupBlankSessions: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
function deepEqual(a: unknown, b: unknown): boolean {
|
function deepEqual(a: unknown, b: unknown): boolean {
|
||||||
@@ -98,6 +100,7 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
|
|||||||
toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion,
|
toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion,
|
||||||
diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultPreferences.diagnosticsExpansion,
|
diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultPreferences.diagnosticsExpansion,
|
||||||
showUsageMetrics: sanitized.showUsageMetrics ?? defaultPreferences.showUsageMetrics,
|
showUsageMetrics: sanitized.showUsageMetrics ?? defaultPreferences.showUsageMetrics,
|
||||||
|
autoCleanupBlankSessions: sanitized.autoCleanupBlankSessions ?? defaultPreferences.autoCleanupBlankSessions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +288,11 @@ function toggleUsageMetrics(): void {
|
|||||||
updatePreferences({ showUsageMetrics: !preferences().showUsageMetrics })
|
updatePreferences({ showUsageMetrics: !preferences().showUsageMetrics })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleAutoCleanupBlankSessions(): void {
|
||||||
|
console.log("toggle auto cleanup")
|
||||||
|
updatePreferences({ autoCleanupBlankSessions: !preferences().autoCleanupBlankSessions })
|
||||||
|
}
|
||||||
|
|
||||||
function addRecentFolder(path: string): void {
|
function addRecentFolder(path: string): void {
|
||||||
updateConfig((draft) => {
|
updateConfig((draft) => {
|
||||||
draft.recentFolders = buildRecentFolderList(path, draft.recentFolders)
|
draft.recentFolders = buildRecentFolderList(path, draft.recentFolders)
|
||||||
@@ -386,6 +394,7 @@ interface ConfigContextValue {
|
|||||||
updateConfig: typeof updateConfig
|
updateConfig: typeof updateConfig
|
||||||
toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks
|
toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks
|
||||||
toggleUsageMetrics: typeof toggleUsageMetrics
|
toggleUsageMetrics: typeof toggleUsageMetrics
|
||||||
|
toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions
|
||||||
setDiffViewMode: typeof setDiffViewMode
|
setDiffViewMode: typeof setDiffViewMode
|
||||||
setToolOutputExpansion: typeof setToolOutputExpansion
|
setToolOutputExpansion: typeof setToolOutputExpansion
|
||||||
setDiagnosticsExpansion: typeof setDiagnosticsExpansion
|
setDiagnosticsExpansion: typeof setDiagnosticsExpansion
|
||||||
@@ -418,6 +427,7 @@ const configContextValue: ConfigContextValue = {
|
|||||||
updateConfig,
|
updateConfig,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
|
toggleAutoCleanupBlankSessions,
|
||||||
setDiffViewMode,
|
setDiffViewMode,
|
||||||
setToolOutputExpansion,
|
setToolOutputExpansion,
|
||||||
setDiagnosticsExpansion,
|
setDiagnosticsExpansion,
|
||||||
@@ -473,6 +483,7 @@ export {
|
|||||||
updateConfig,
|
updateConfig,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
recentFolders,
|
recentFolders,
|
||||||
addRecentFolder,
|
addRecentFolder,
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ import type { Session } from "../types/session"
|
|||||||
import type { Message } from "../types/message"
|
import type { Message } from "../types/message"
|
||||||
|
|
||||||
import { instances, refreshPermissionsForSession } from "./instances"
|
import { instances, refreshPermissionsForSession } from "./instances"
|
||||||
import { setAgentModelPreference } from "./preferences"
|
import { preferences, setAgentModelPreference } from "./preferences"
|
||||||
import { setSessionCompactionState } from "./session-compaction"
|
import { setSessionCompactionState } from "./session-compaction"
|
||||||
import {
|
import {
|
||||||
activeSessionId,
|
activeSessionId,
|
||||||
agents,
|
agents,
|
||||||
clearSessionDraftPrompt,
|
clearSessionDraftPrompt,
|
||||||
|
getChildSessions,
|
||||||
|
isBlankSession,
|
||||||
messagesLoaded,
|
messagesLoaded,
|
||||||
providers,
|
|
||||||
pruneDraftPrompts,
|
pruneDraftPrompts,
|
||||||
|
providers,
|
||||||
setActiveSessionId,
|
setActiveSessionId,
|
||||||
setAgents,
|
setAgents,
|
||||||
setMessagesLoaded,
|
setMessagesLoaded,
|
||||||
@@ -20,6 +22,7 @@ import {
|
|||||||
sessions,
|
sessions,
|
||||||
loading,
|
loading,
|
||||||
setLoading,
|
setLoading,
|
||||||
|
cleanupBlankSessions,
|
||||||
} from "./session-state"
|
} from "./session-state"
|
||||||
import { DEFAULT_MODEL_OUTPUT_LIMIT, getDefaultModel, isModelValid } from "./session-models"
|
import { DEFAULT_MODEL_OUTPUT_LIMIT, getDefaultModel, isModelValid } from "./session-models"
|
||||||
import { normalizeMessagePart } from "./message-v2/normalizers"
|
import { normalizeMessagePart } from "./message-v2/normalizers"
|
||||||
@@ -228,6 +231,10 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (preferences().autoCleanupBlankSessions) {
|
||||||
|
await cleanupBlankSessions(instanceId, session.id)
|
||||||
|
}
|
||||||
|
|
||||||
return session
|
return session
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create session:", error)
|
console.error("Failed to create session:", error)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { createSignal } from "solid-js"
|
import { createSignal } from "solid-js"
|
||||||
|
|
||||||
import type { Session, Agent, Provider } from "../types/session"
|
import type { Session, Agent, Provider } from "../types/session"
|
||||||
|
import { deleteSession, loadMessages } from "./session-api"
|
||||||
|
import { showToastNotification } from "../lib/notifications"
|
||||||
|
import { messageStoreBus } from "./message-v2/bus"
|
||||||
|
import { instances } from "./instances"
|
||||||
|
import { showConfirmDialog } from "./alerts"
|
||||||
|
|
||||||
export interface SessionInfo {
|
export interface SessionInfo {
|
||||||
cost: number
|
cost: number
|
||||||
@@ -221,6 +226,108 @@ function getSessionInfo(instanceId: string, sessionId: string): SessionInfo | un
|
|||||||
return sessionInfoByInstance().get(instanceId)?.get(sessionId)
|
return sessionInfoByInstance().get(instanceId)?.get(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function isBlankSession(session: Session, instanceId: string, fetchIfNeeded = false): Promise<boolean> {
|
||||||
|
const created = session.time?.created || 0
|
||||||
|
const updated = session.time?.updated || 0
|
||||||
|
const hasChildren = getChildSessions(instanceId, session.id).length > 0
|
||||||
|
const isFreshSession = created === updated && !hasChildren
|
||||||
|
|
||||||
|
// Common short-circuit: fresh sessions without children
|
||||||
|
if (!fetchIfNeeded) {
|
||||||
|
return isFreshSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a more thorough deep clean, we need to look at actual messages
|
||||||
|
|
||||||
|
const instance = instances().get(instanceId)
|
||||||
|
if (!instance?.client) {
|
||||||
|
return isFreshSession
|
||||||
|
}
|
||||||
|
let messages: any[] = []
|
||||||
|
try {
|
||||||
|
const response = await instance.client.session.messages({ path: { id: session.id } })
|
||||||
|
messages = response.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch messages for session ${session.id}:`, error)
|
||||||
|
return isFreshSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific logic by session type
|
||||||
|
if (session.parentId === null) {
|
||||||
|
// Parent: blank if no messages and no children (fresh !== blank sometimes!)
|
||||||
|
const hasChildren = getChildSessions(instanceId, session.id).length > 0
|
||||||
|
return messages.length === 0 && !hasChildren
|
||||||
|
} else if (session.title?.includes("subagent)")) {
|
||||||
|
// Subagent: "blank" (really: finished doing its job) if actually blank...
|
||||||
|
// ... OR no streaming, no pending perms, no tool parts
|
||||||
|
if (messages.length === 0) return true
|
||||||
|
|
||||||
|
const hasStreaming = messages.some((msg) => {
|
||||||
|
const info = msg.info.status || msg.status
|
||||||
|
return info === "streaming" || info === "sending"
|
||||||
|
})
|
||||||
|
|
||||||
|
const lastMessage = messages[messages.length - 1]
|
||||||
|
const lastParts = lastMessage?.parts || []
|
||||||
|
const hasToolPart = lastParts.some((part: any) =>
|
||||||
|
part.type === "tool" || part.data?.type === "tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
return !hasStreaming && !session.pendingPermission && !hasToolPart
|
||||||
|
} else {
|
||||||
|
// Fork: blank if somehow has no messages or at revert point
|
||||||
|
if (messages.length === 0) return true
|
||||||
|
|
||||||
|
const lastMessage = messages[messages.length - 1]
|
||||||
|
const lastInfo = lastMessage?.info || lastMessage
|
||||||
|
return lastInfo?.id === session.revert?.messageID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function cleanupBlankSessions(instanceId: string, excludeSessionId?: string, fetchIfNeeded = false): Promise<void> {
|
||||||
|
const instanceSessions = sessions().get(instanceId)
|
||||||
|
if (!instanceSessions) return
|
||||||
|
|
||||||
|
if (fetchIfNeeded) {
|
||||||
|
const confirmed = await showConfirmDialog(
|
||||||
|
"This cleanup may be slow, and may delete sessions you didn't intend to delete. Are you sure?",
|
||||||
|
{
|
||||||
|
title: "Deep Clean Sessions",
|
||||||
|
detail: "Deep Clean Sessions will delete all sessions that have no messages, remove any finished sub-agent sessions, and clear out any unused forks of a session.",
|
||||||
|
confirmLabel: "Continue",
|
||||||
|
cancelLabel: "Cancel"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!confirmed) return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupPromises = Array.from(instanceSessions)
|
||||||
|
.filter(([sessionId]) => sessionId !== excludeSessionId)
|
||||||
|
.map(async ([sessionId, session]) => {
|
||||||
|
const isBlank = await isBlankSession(session, instanceId, fetchIfNeeded)
|
||||||
|
if (!isBlank) return false
|
||||||
|
|
||||||
|
await deleteSession(instanceId, sessionId).catch((error: Error) => {
|
||||||
|
console.error(`Failed to delete blank session ${sessionId}:`, error)
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cleanupPromises.length > 0) {
|
||||||
|
console.log(`Cleaning up ${cleanupPromises.length} blank sessions`)
|
||||||
|
const deletionResults = await Promise.all(cleanupPromises)
|
||||||
|
const deletedCount = deletionResults.filter(Boolean).length
|
||||||
|
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
showToastNotification({
|
||||||
|
message: `Cleaned up ${deletedCount} blank session${deletedCount === 1 ? "" : "s"}`,
|
||||||
|
variant: "info"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
sessions,
|
sessions,
|
||||||
setSessions,
|
setSessions,
|
||||||
@@ -259,4 +366,6 @@ export {
|
|||||||
isSessionBusy,
|
isSessionBusy,
|
||||||
isSessionMessagesLoading,
|
isSessionMessagesLoading,
|
||||||
getSessionInfo,
|
getSessionInfo,
|
||||||
|
isBlankSession,
|
||||||
|
cleanupBlankSessions,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user