mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-17 09:41:58 +00:00
* fix(desktop): keep chat recents focused and reset hotkey target
Exclude messaging platform threads from chat recents pagination so Load More returns chat sessions, and clear stale quick-create profile state before Ctrl+N starts a new session.
* fix(desktop): surface new sessions in sidebar + unstick new-chat Thinking
Two renderer regressions in the desktop chat app:
- Sidebar ordering: orderByIds/reconcileOrderIds appended ids missing from
the persisted order to the BOTTOM. Callers pass recency-sorted lists
(newest first), so a brand-new Ctrl+N session sank below the saved order
and read as "my latest session never showed up". Prepend fresh ids so new
activity surfaces at the top.
- New-chat stuck on "Thinking": terminal/attention state transitions
(turn finished, error, or agent now waiting on user) were RAF-batched.
Electron throttles requestAnimationFrame to ~0 while the window is
backgrounded, occluded, or unfocused, stranding the deferred flush. Flush
critical transitions (!busy || needsInput) synchronously; keep the busy
heartbeat RAF-batched to avoid scroll churn.
Does not touch the messaging-source exclusion in chat recents queries.
* fix(desktop): stop excluding messaging platforms from chat recents
The "keep chat recents focused" change excluded every messaging-platform
source (telegram, discord, slack, …) from the recents query. That silently
undid the messaging-source-folder feature already on main (ede4f5a4a): the
sidebar builds those folders purely from the loaded recents page, so once the
sources were filtered out the folders never rendered — telegram and friends
vanished from the left sidebar.
Only cron stays excluded (it has its own dedicated section). Messaging
sessions belong in the sidebar and render with their platform folder/icon.
Removes the now-unused MESSAGING_SESSION_SOURCE_IDS export.
* fix(desktop): give each messaging platform its own self-managed sidebar section
Recents are local-only again: cron and every messaging platform are excluded
from the chat-recents query, so "Load more" pages through interactive local
chats instead of interleaving gateway threads that bury them.
Each messaging platform (telegram, discord, ...) is now fetched as its own
slice (refreshMessagingSessions) and rendered as a self-managed sidebar
section with its platform icon, count, and per-platform "load more" — no
source-grouping magic inside recents.
Handed-off sessions (live source becomes local after a handoff) keep their
origin-platform badge on the row via handoff_platform, so a Telegram thread
continued in the desktop still reads as Telegram.
* fix(desktop): self-heal a stranded routed session in route-resume
An intermittent create/stream race can leave selected/active session ids
null while the route stays on /:sid — the transcript then sticks empty
even though the turn completed and persisted (the "second Ctrl+N shows no
response" symptom). The pathname didn't change, so route-resume's normal
gate skipped and the view stayed stuck.
Resume whenever the routed session isn't the loaded one, gated on
freshDraftReady so the /:sid -> /new transition (which also momentarily
nulls selected/active a render before the pathname flips) is NOT treated
as stranded. selectedStoredSessionIdRef is set synchronously at resume
entry, so this can't loop, and the resume cached fast-path restores the
already-streamed messages without a refetch.
* fix(desktop): bypass smooth reveal on primary markdown stream
Render main assistant text through deferred markdown directly instead of the smooth-reveal wrapper. This isolates the wrapper to reasoning surfaces and avoids the intermittent blank-response regression after consecutive new-session flows.
126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
const SOURCE_LABELS: Record<string, string> = {
|
|
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<string, string[]> = {
|
|
bluebubbles: ['apple messages', 'imessage'],
|
|
cli: ['terminal'],
|
|
desktop: ['app', 'gui'],
|
|
local: ['machine'],
|
|
qqbot: ['qq'],
|
|
telegram: ['tg'],
|
|
tui: ['terminal'],
|
|
weixin: ['wechat'],
|
|
whatsapp: ['wa']
|
|
}
|
|
|
|
// Sources that run on the local machine rather than an external messaging
|
|
// platform. A handoff *from* one of these isn't a platform origin worth a badge.
|
|
// Exported so the recents fetch can keep these in the main list while the
|
|
// messaging fetch excludes them.
|
|
export const LOCAL_SESSION_SOURCE_IDS = ['cli', 'codex', 'desktop', 'gateway', 'local', 'tui']
|
|
const LOCAL_SOURCE_IDS = new Set(LOCAL_SESSION_SOURCE_IDS)
|
|
|
|
// External messaging platforms that each get their own self-managed sidebar
|
|
// section (fetched separately from local recents). Mirrors the gateway platform
|
|
// adapters; keep in sync with PLATFORM_ICONS in app/messaging/platform-icon.tsx.
|
|
export const MESSAGING_SESSION_SOURCE_IDS = [
|
|
'telegram',
|
|
'discord',
|
|
'slack',
|
|
'mattermost',
|
|
'matrix',
|
|
'signal',
|
|
'whatsapp',
|
|
'bluebubbles',
|
|
'homeassistant',
|
|
'email',
|
|
'sms',
|
|
'webhook',
|
|
'api_server',
|
|
'weixin',
|
|
'wecom',
|
|
'qqbot',
|
|
'yuanbao',
|
|
'dingtalk',
|
|
'feishu'
|
|
]
|
|
const MESSAGING_SOURCE_IDS = new Set(MESSAGING_SESSION_SOURCE_IDS)
|
|
|
|
/** True when a source id is an external messaging platform (gets its own
|
|
* sidebar section) rather than a local/CLI/desktop session. */
|
|
export function isMessagingSource(source: null | string | undefined): boolean {
|
|
const id = normalizeSessionSource(source)
|
|
|
|
return id != null && MESSAGING_SOURCE_IDS.has(id)
|
|
}
|
|
|
|
export function normalizeSessionSource(source: null | string | undefined): string | null {
|
|
const id = source?.trim().toLowerCase()
|
|
|
|
return id || null
|
|
}
|
|
|
|
/**
|
|
* Resolve the origin messaging platform for a handed-off session. Returns the
|
|
* normalized platform id (e.g. 'telegram') when the session completed a handoff
|
|
* from a real messaging platform, otherwise null. After a handoff the live
|
|
* source is local, so this is what drives the row's origin-platform badge.
|
|
*/
|
|
export function handoffOriginSource(
|
|
handoffState: null | string | undefined,
|
|
handoffPlatform: null | string | undefined
|
|
): string | null {
|
|
if (handoffState !== 'completed') {
|
|
return null
|
|
}
|
|
|
|
const id = normalizeSessionSource(handoffPlatform)
|
|
|
|
if (!id || LOCAL_SOURCE_IDS.has(id)) {
|
|
return null
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
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)
|
|
}
|