diff --git a/apps/desktop/src/app/chat/sidebar/session-row.tsx b/apps/desktop/src/app/chat/sidebar/session-row.tsx
index 0c2ed62d235..16d5baa8a4c 100644
--- a/apps/desktop/src/app/chat/sidebar/session-row.tsx
+++ b/apps/desktop/src/app/chat/sidebar/session-row.tsx
@@ -2,12 +2,14 @@ import { useStore } from '@nanostores/react'
import type * as React from 'react'
import { writeSessionDrag } from '@/app/chat/composer/inline-refs'
+import { PlatformAvatar } from '@/app/messaging/platform-icon'
import { Button } from '@/components/ui/button'
import { Codicon } from '@/components/ui/codicon'
import type { SessionInfo } from '@/hermes'
import { type Translations, useI18n } from '@/i18n'
import { sessionTitle } from '@/lib/chat-runtime'
import { triggerHaptic } from '@/lib/haptics'
+import { normalizeSessionSource, sessionSourceLabel } from '@/lib/session-source'
import { cn } from '@/lib/utils'
import { $attentionSessionIds } from '@/store/session'
@@ -66,6 +68,9 @@ export function SidebarSessionRow({
const r = t.sidebar.row
const title = sessionTitle(session)
const age = formatAge(session.last_active || session.started_at, r)
+ const sourceId = normalizeSessionSource(session.source)
+ const sourceLabel = sessionSourceLabel(sourceId)
+ const showSource = Boolean(sourceId && sourceLabel && !['desktop', 'local', 'tui'].includes(sourceId))
const handleLabel = `Reorder ${title}`
// Subscribe per-row (the leaf) instead of drilling a set through the list —
// the atom is tiny and rarely non-empty. True when a clarify prompt in this
@@ -176,12 +181,25 @@ export function SidebarSessionRow({
needsInput ? 'overflow-visible' : 'overflow-hidden'
)}
>
-
-
+
+
)}
{title}
+ {showSource && sourceId && sourceLabel && (
+
+
+ {sourceLabel}
+
+ )}
{!isWorking && (
diff --git a/apps/desktop/src/app/messaging/platform-icon.tsx b/apps/desktop/src/app/messaging/platform-icon.tsx
index 6a0b32a7a81..4a6be4354db 100644
--- a/apps/desktop/src/app/messaging/platform-icon.tsx
+++ b/apps/desktop/src/app/messaging/platform-icon.tsx
@@ -28,15 +28,17 @@ import { cn } from '@/lib/utils'
type IconKind = 'brand' | 'generic'
interface PlatformIconSpec {
- Icon: ComponentType>
+ Icon?: ComponentType>
color: string
kind: IconKind
+ monogram?: string
}
const PLATFORM_ICONS: Record = {
telegram: { Icon: SiTelegram, color: '#26A5E4', kind: 'brand' },
discord: { Icon: SiDiscord, color: '#5865F2', kind: 'brand' },
// Slack removed from Simple Icons by Salesforce request — letter monogram.
+ slack: { color: '#4A154B', kind: 'brand', monogram: 'S' },
mattermost: { Icon: SiMattermost, color: '#0058CC', kind: 'brand' },
matrix: { Icon: SiMatrix, color: '#000000', kind: 'brand' },
signal: { Icon: SiSignal, color: '#3A76F0', kind: 'brand' },
@@ -87,7 +89,7 @@ export function PlatformAvatar({ className, platformId, platformName }: Platform
color
}}
>
-
+ {Icon ? : spec.monogram || platformName.charAt(0).toUpperCase()}
)
}
diff --git a/apps/desktop/src/lib/session-search.test.ts b/apps/desktop/src/lib/session-search.test.ts
index aa40fe59c0c..00027ff3186 100644
--- a/apps/desktop/src/lib/session-search.test.ts
+++ b/apps/desktop/src/lib/session-search.test.ts
@@ -52,6 +52,14 @@ describe('sessionMatchesSearch', () => {
expect(sessionMatchesSearch(session, 'hermes-agent')).toBe(true)
})
+ it('matches sessions by source platform and aliases', () => {
+ expect(sessionMatchesSearch(makeSession({ source: 'telegram' }), 'Telegram')).toBe(true)
+ expect(sessionMatchesSearch(makeSession({ source: 'whatsapp' }), 'WhatsApp')).toBe(true)
+ expect(sessionMatchesSearch(makeSession({ source: 'whatsapp' }), 'wa')).toBe(true)
+ expect(sessionMatchesSearch(makeSession({ source: 'slack' }), 'slack')).toBe(true)
+ expect(sessionMatchesSearch(makeSession({ source: 'bluebubbles' }), 'imessage')).toBe(true)
+ })
+
it('does not match unrelated queries', () => {
expect(sessionMatchesSearch(makeSession(), 'totally-unrelated')).toBe(false)
})
diff --git a/apps/desktop/src/lib/session-search.ts b/apps/desktop/src/lib/session-search.ts
index b8ee6ebf30c..6ec6dde85e4 100644
--- a/apps/desktop/src/lib/session-search.ts
+++ b/apps/desktop/src/lib/session-search.ts
@@ -1,6 +1,7 @@
import type { SessionInfo } from '@/types/hermes'
import { sessionTitle } from './chat-runtime'
+import { sessionSourceSearchTerms } from './session-source'
export function sessionMatchesSearch(session: SessionInfo, query: string): boolean {
const needle = query.trim().toLowerCase()
@@ -14,6 +15,7 @@ export function sessionMatchesSearch(session: SessionInfo, query: string): boole
session._lineage_root_id ?? '',
sessionTitle(session),
session.preview ?? '',
- session.cwd ?? ''
+ session.cwd ?? '',
+ ...sessionSourceSearchTerms(session.source)
].some(value => value.toLowerCase().includes(needle))
}
diff --git a/apps/desktop/src/lib/session-source.ts b/apps/desktop/src/lib/session-source.ts
new file mode 100644
index 00000000000..8940999985f
--- /dev/null
+++ b/apps/desktop/src/lib/session-source.ts
@@ -0,0 +1,62 @@
+const SOURCE_LABELS: Record = {
+ api_server: 'API',
+ bluebubbles: 'iMessage',
+ cli: 'CLI',
+ codex: 'Codex',
+ desktop: 'Desktop',
+ discord: 'Discord',
+ email: 'Email',
+ gateway: 'Gateway',
+ local: 'Local',
+ matrix: 'Matrix',
+ mattermost: 'Mattermost',
+ qqbot: 'QQ',
+ signal: 'Signal',
+ slack: 'Slack',
+ sms: 'SMS',
+ telegram: 'Telegram',
+ tui: 'TUI',
+ webhook: 'Webhook',
+ weixin: 'WeChat',
+ whatsapp: 'WhatsApp',
+ yuanbao: 'Yuanbao'
+}
+
+const SOURCE_ALIASES: Record = {
+ bluebubbles: ['apple messages', 'imessage'],
+ cli: ['terminal'],
+ desktop: ['app', 'gui'],
+ local: ['machine'],
+ qqbot: ['qq'],
+ telegram: ['tg'],
+ tui: ['terminal'],
+ weixin: ['wechat'],
+ whatsapp: ['wa']
+}
+
+export function normalizeSessionSource(source: null | string | undefined): string | null {
+ const id = source?.trim().toLowerCase()
+
+ return id || null
+}
+
+export function sessionSourceLabel(source: null | string | undefined): string | null {
+ const id = normalizeSessionSource(source)
+
+ if (!id) {
+ return null
+ }
+
+ return SOURCE_LABELS[id] || id.replace(/[_-]+/g, ' ').replace(/\b\w/g, char => char.toUpperCase())
+}
+
+export function sessionSourceSearchTerms(source: null | string | undefined): string[] {
+ const id = normalizeSessionSource(source)
+ const label = sessionSourceLabel(id)
+
+ if (!id) {
+ return []
+ }
+
+ return [id, label ?? '', ...(SOURCE_ALIASES[id] ?? [])].filter(Boolean)
+}