diff --git a/desktop/src/components/layout/Sidebar.test.tsx b/desktop/src/components/layout/Sidebar.test.tsx
index 473c41a16..9a35c8a7a 100644
--- a/desktop/src/components/layout/Sidebar.test.tsx
+++ b/desktop/src/components/layout/Sidebar.test.tsx
@@ -102,6 +102,7 @@ vi.mock('../../i18n', () => ({
import { Sidebar } from './Sidebar'
import { useChatStore } from '../../stores/chatStore'
import { useSessionStore } from '../../stores/sessionStore'
+import { useSettingsStore } from '../../stores/settingsStore'
import { useTabStore } from '../../stores/tabStore'
import { useUIStore } from '../../stores/uiStore'
import type { SessionListItem } from '../../types/session'
@@ -1046,3 +1047,87 @@ describe('Sidebar', () => {
})
})
})
+
+describe('Sidebar observer session filtering', () => {
+ const fetchSessions = vi.fn()
+
+ beforeEach(() => {
+ fetchSessions.mockReset()
+ useTabStore.setState({ tabs: [], activeTabId: null })
+ useSessionStore.setState({
+ sessions: [],
+ activeSessionId: null,
+ isLoading: false,
+ error: null,
+ selectedProjects: [],
+ availableProjects: [],
+ isBatchMode: false,
+ selectedSessionIds: new Set(),
+ fetchSessions,
+ createSession: vi.fn(),
+ deleteSession: vi.fn(),
+ deleteSessions: vi.fn(),
+ })
+ useUIStore.setState({
+ sidebarOpen: true,
+ addToast: vi.fn(),
+ } as Partial>)
+ useSettingsStore.setState({ observerSessionsHidden: false })
+ })
+
+ afterEach(() => {
+ cleanup()
+ })
+
+ const observerSession = {
+ id: 'observer-1',
+ title: 'Hello memory agent',
+ createdAt: new Date().toISOString(),
+ modifiedAt: new Date().toISOString(),
+ messageCount: 5,
+ projectPath: '-Users-test--claude-mem-observer-sessions',
+ workDir: '/Users/test/.claude-mem/observer-sessions',
+ workDirExists: true,
+ }
+
+ const normalSession = {
+ id: 'normal-1',
+ title: 'Bug fix in auth',
+ createdAt: new Date().toISOString(),
+ modifiedAt: new Date().toISOString(),
+ messageCount: 3,
+ projectPath: '-Users-test-my-project',
+ workDir: '/Users/test/my-project',
+ workDirExists: true,
+ }
+
+ it('shows observer sessions when toggle is off', () => {
+ useSettingsStore.setState({ observerSessionsHidden: false })
+ useSessionStore.setState({ sessions: [normalSession, observerSession] })
+
+ render()
+
+ expect(screen.getByText('Hello memory agent')).toBeInTheDocument()
+ expect(screen.getByText('Bug fix in auth')).toBeInTheDocument()
+ })
+
+ it('hides observer sessions when toggle is on', () => {
+ useSettingsStore.setState({ observerSessionsHidden: true })
+ useSessionStore.setState({ sessions: [normalSession, observerSession] })
+
+ render()
+
+ expect(screen.queryByText('Hello memory agent')).not.toBeInTheDocument()
+ expect(screen.getByText('Bug fix in auth')).toBeInTheDocument()
+ })
+
+ it('shows no sessions message when all sessions are filtered out', () => {
+ useSettingsStore.setState({ observerSessionsHidden: true })
+ useSessionStore.setState({ sessions: [observerSession] })
+
+ render()
+
+ expect(screen.queryByText('Hello memory agent')).not.toBeInTheDocument()
+ expect(screen.getByText('No sessions')).toBeInTheDocument()
+ })
+})
diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx
index a4593ddfd..ee50296aa 100644
--- a/desktop/src/components/layout/Sidebar.tsx
+++ b/desktop/src/components/layout/Sidebar.tsx
@@ -9,6 +9,7 @@ import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tab
import { useChatStore } from '../../stores/chatStore'
import { useOpenTargetStore } from '../../stores/openTargetStore'
import { desktopUiPreferencesApi, type SidebarProjectPreferences } from '../../api/desktopUiPreferences'
+import { useSettingsStore } from '../../stores/settingsStore'
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window)
const isWindows = typeof navigator !== 'undefined' && /Win/.test(navigator.platform)
@@ -61,6 +62,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
const activeTabId = useTabStore((s) => s.activeTabId)
const closeTab = useTabStore((s) => s.closeTab)
const disconnectSession = useChatStore((s) => s.disconnectSession)
+ const observerSessionsHidden = useSettingsStore((s) => s.observerSessionsHidden)
const [searchQuery, setSearchQuery] = useState('')
const [contextMenu, setContextMenu] = useState<{ id: string; x: number; y: number } | null>(null)
const [projectContextMenu, setProjectContextMenu] = useState<{ key: string; x: number; y: number } | null>(null)
@@ -105,12 +107,20 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
const filteredSessions = useMemo(() => {
let result = sessions
+ if (observerSessionsHidden) {
+ result = result.filter((s) => {
+ const p = (s.projectPath || '').toLowerCase()
+ const w = (s.workDir || '').toLowerCase()
+ return !p.includes('claude-mem') && !p.includes('claude_mem') &&
+ !w.includes('/.claude-mem/observer-sessions')
+ })
+ }
if (searchQuery) {
const q = searchQuery.toLowerCase()
result = result.filter((s) => s.title.toLowerCase().includes(q))
}
return result
- }, [sessions, searchQuery])
+ }, [sessions, searchQuery, observerSessionsHidden])
const projectGroups = useMemo(() => groupByProject(filteredSessions, projectSortBy), [filteredSessions, projectSortBy])
const orderedProjectGroups = useMemo(
diff --git a/desktop/src/components/plugins/PluginDetail.tsx b/desktop/src/components/plugins/PluginDetail.tsx
index abe9f5d66..4216033e3 100644
--- a/desktop/src/components/plugins/PluginDetail.tsx
+++ b/desktop/src/components/plugins/PluginDetail.tsx
@@ -10,6 +10,7 @@ import { SETTINGS_TAB_ID, useTabStore } from '../../stores/tabStore'
import { useSkillStore } from '../../stores/skillStore'
import { useAgentStore } from '../../stores/agentStore'
import { useMcpStore } from '../../stores/mcpStore'
+import { useSettingsStore } from '../../stores/settingsStore'
const CAPABILITY_ORDER: PluginCapabilityKey[] = [
'lspServers',
@@ -35,6 +36,8 @@ export function PluginDetail() {
const selectAgent = useAgentStore((s) => s.selectAgent)
const fetchServers = useMcpStore((s) => s.fetchServers)
const selectServer = useMcpStore((s) => s.selectServer)
+ const observerSessionsHidden = useSettingsStore((s) => s.observerSessionsHidden)
+ const setObserverSessionsHidden = useSettingsStore((s) => s.setObserverSessionsHidden)
const t = useTranslation()
const [actionKey, setActionKey] = useState(null)
const [showUninstallDialog, setShowUninstallDialog] = useState(false)
@@ -309,6 +312,41 @@ export function PluginDetail() {
+ {selectedPlugin.id === 'claude-mem@thedotmack' && (
+
+
+
+ )}
+
{selectedPlugin.errors.length > 0 && (
diff --git a/desktop/src/stores/settingsStore.test.ts b/desktop/src/stores/settingsStore.test.ts
index 8c45f1725..fdac50ab3 100644
--- a/desktop/src/stores/settingsStore.test.ts
+++ b/desktop/src/stores/settingsStore.test.ts
@@ -597,3 +597,108 @@ describe('settingsStore H5 access behavior', () => {
expect('h5AccessGeneratedToken' in useSettingsStore.getState()).toBe(false)
})
})
+
+describe('settingsStore observer sessions hidden', () => {
+ beforeEach(() => {
+ vi.resetModules()
+ vi.clearAllMocks()
+ })
+
+ it('defaults to false (observer sessions visible)', async () => {
+ const { useSettingsStore } = await import('./settingsStore')
+
+ expect(useSettingsStore.getState().observerSessionsHidden).toBe(false)
+ })
+
+ it('hydrates claudeMemObserverSessionsHidden from user settings', async () => {
+ vi.doMock('../api/settings', () => ({
+ settingsApi: {
+ getUser: vi.fn().mockResolvedValue({ claudeMemObserverSessionsHidden: true }),
+ updateUser: vi.fn(),
+ getPermissionMode: vi.fn().mockResolvedValue({ mode: 'default' }),
+ setPermissionMode: vi.fn(),
+ getCliLauncherStatus: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/models', () => ({
+ modelsApi: {
+ list: vi.fn().mockResolvedValue({ models: [] }),
+ getCurrent: vi.fn().mockResolvedValue({ model: null }),
+ setCurrent: vi.fn(),
+ getEffort: vi.fn().mockResolvedValue({ level: 'medium' }),
+ setEffort: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/h5Access', () => ({
+ h5AccessApi: {
+ get: vi.fn().mockResolvedValue({
+ settings: {
+ enabled: false,
+ tokenPreview: null,
+ allowedOrigins: [],
+ publicBaseUrl: null,
+ },
+ }),
+ enable: vi.fn(),
+ disable: vi.fn(),
+ regenerate: vi.fn(),
+ update: vi.fn(),
+ },
+ }))
+
+ const { useSettingsStore } = await import('./settingsStore')
+
+ await useSettingsStore.getState().fetchAll()
+
+ expect(useSettingsStore.getState().observerSessionsHidden).toBe(true)
+ })
+
+ it('persists toggle state to user settings', async () => {
+ const updateUser = vi.fn().mockResolvedValue({ ok: true })
+
+ vi.doMock('../api/settings', () => ({
+ settingsApi: {
+ getUser: vi.fn(),
+ updateUser,
+ getPermissionMode: vi.fn(),
+ setPermissionMode: vi.fn(),
+ getCliLauncherStatus: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/models', () => ({
+ modelsApi: {
+ list: vi.fn(),
+ getCurrent: vi.fn(),
+ setCurrent: vi.fn(),
+ getEffort: vi.fn(),
+ setEffort: vi.fn(),
+ },
+ }))
+ vi.doMock('../api/h5Access', () => ({
+ h5AccessApi: {
+ get: vi.fn().mockResolvedValue({
+ settings: {
+ enabled: false,
+ tokenPreview: null,
+ allowedOrigins: [],
+ publicBaseUrl: null,
+ },
+ }),
+ enable: vi.fn(),
+ disable: vi.fn(),
+ regenerate: vi.fn(),
+ update: vi.fn(),
+ },
+ }))
+
+ const { useSettingsStore } = await import('./settingsStore')
+
+ await useSettingsStore.getState().setObserverSessionsHidden(true)
+ expect(useSettingsStore.getState().observerSessionsHidden).toBe(true)
+ expect(updateUser).toHaveBeenCalledWith({ claudeMemObserverSessionsHidden: true })
+
+ await useSettingsStore.getState().setObserverSessionsHidden(false)
+ expect(useSettingsStore.getState().observerSessionsHidden).toBe(false)
+ expect(updateUser).toHaveBeenCalledWith({ claudeMemObserverSessionsHidden: undefined })
+ })
+})
diff --git a/desktop/src/stores/settingsStore.ts b/desktop/src/stores/settingsStore.ts
index 51e628ab6..71fafcca1 100644
--- a/desktop/src/stores/settingsStore.ts
+++ b/desktop/src/stores/settingsStore.ts
@@ -47,6 +47,7 @@ type SettingsStore = {
h5AccessError: string | null
responseLanguage: string
uiZoom: number
+ observerSessionsHidden: boolean
isLoading: boolean
error: string | null
@@ -70,6 +71,7 @@ type SettingsStore = {
}) => Promise
setResponseLanguage: (language: string) => Promise
setUiZoom: (zoom: number) => void
+ setObserverSessionsHidden: (hidden: boolean) => Promise
}
const DEFAULT_H5_ACCESS_SETTINGS: H5AccessSettings = {
@@ -95,6 +97,7 @@ export const useSettingsStore = create((set, get) => ({
h5AccessError: null,
responseLanguage: '',
uiZoom: readStoredAppZoomLevel(),
+ observerSessionsHidden: false,
isLoading: false,
error: null,
@@ -132,6 +135,7 @@ export const useSettingsStore = create((set, get) => ({
h5Access: h5AccessResult.settings,
h5AccessError: h5AccessResult.error,
responseLanguage: typeof userSettings.language === 'string' ? userSettings.language : '',
+ observerSessionsHidden: userSettings.claudeMemObserverSessionsHidden === true,
isLoading: false,
error: null,
})
@@ -310,6 +314,16 @@ export const useSettingsStore = create((set, get) => ({
set({ responseLanguage: prev })
}
},
+
+ setObserverSessionsHidden: async (hidden) => {
+ const prev = get().observerSessionsHidden
+ set({ observerSessionsHidden: hidden })
+ try {
+ await settingsApi.updateUser({ claudeMemObserverSessionsHidden: hidden || undefined })
+ } catch {
+ set({ observerSessionsHidden: prev })
+ }
+ },
}))
function normalizeWebSearchSettings(settings: WebSearchSettings | undefined): WebSearchSettings {