mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-30 11:52:04 +00:00
fix(desktop): deduplicate sidebar rows by compression lineage in mergeSessionPage (#43487)
When auto-compression rotates the session tip (old #4 → new #5), the incoming page carries the new tip but the previous list still holds the old one. The old tip's id differs from the new tip's id, so the existing id-only dedup in mergeSessionPage() preserves both as separate sidebar rows. Add lineage-level dedup: build a set of incoming lineage keys (`_lineage_root_id ?? id`) and filter survivors whose lineage key matches any incoming row. This mirrors the existing sessionPinId() logic used for pin stability. Fixes #43483
This commit is contained in:
parent
c94e93a648
commit
0d3e2cc539
2 changed files with 49 additions and 2 deletions
|
|
@ -133,13 +133,52 @@ describe('mergeSessionPage', () => {
|
|||
it('keeps a pinned session matched by its lineage root after compression', () => {
|
||||
// The pin is stored on the lineage-root id, but the loaded row surfaces
|
||||
// under its live compression tip. Matching on _lineage_root_id keeps it.
|
||||
const previous = [session({ id: 'tip', _lineage_root_id: 'root' })]
|
||||
const incoming = [session({ id: 'other' })]
|
||||
const previous = [session({ id: 'tip', _lineage_root_id: 'root' })] as SessionInfo[]
|
||||
const incoming = [session({ id: 'other' })] as SessionInfo[]
|
||||
|
||||
const merged = mergeSessionPage(previous, incoming, ['root'])
|
||||
|
||||
expect(merged.map(s => s.id)).toEqual(['tip', 'other'])
|
||||
})
|
||||
|
||||
it('evicts an old compression tip when the incoming page has the new tip from the same lineage', () => {
|
||||
// Repro of #43483: after auto-compression rotates the tip (#4 → #5),
|
||||
// the sidebar showed both the old tip and the new tip as separate rows.
|
||||
// The old tip must be evicted because its lineage key matches the incoming
|
||||
// new tip's lineage key.
|
||||
const previous = [
|
||||
session({ id: 'tip-4', _lineage_root_id: 'root' }),
|
||||
session({ id: 'other' }),
|
||||
] as SessionInfo[]
|
||||
const incoming = [
|
||||
session({ id: 'tip-5', _lineage_root_id: 'root' }),
|
||||
] as SessionInfo[]
|
||||
|
||||
// 'tip-4' is in the keep set (e.g. it was the active/working session),
|
||||
// but should still be evicted because the incoming page carries the same
|
||||
// lineage under a new tip id.
|
||||
const merged = mergeSessionPage(previous, incoming, ['tip-4'])
|
||||
|
||||
expect(merged.map(s => s.id)).toEqual(['tip-5'])
|
||||
// The new tip comes from the server payload.
|
||||
expect(merged.find(s => s.id === 'tip-5')?._lineage_root_id).toBe('root')
|
||||
})
|
||||
|
||||
it('preserves an unrelated pinned session even when lineage dedup is active', () => {
|
||||
// Regression guard: lineage dedup must not accidentally evict sessions
|
||||
// from a different lineage that happen to be in the keep set.
|
||||
const previous = [
|
||||
session({ id: 'a-old', _lineage_root_id: 'lineage-a' }),
|
||||
session({ id: 'b', _lineage_root_id: 'lineage-b' }),
|
||||
] as SessionInfo[]
|
||||
const incoming = [
|
||||
session({ id: 'a-new', _lineage_root_id: 'lineage-a' }),
|
||||
] as SessionInfo[]
|
||||
|
||||
const merged = mergeSessionPage(previous, incoming, ['b'])
|
||||
|
||||
expect(merged.map(s => s.id)).toEqual(['b', 'a-new'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('workspaceCwdForNewSession', () => {
|
||||
|
|
|
|||
|
|
@ -125,10 +125,18 @@ export function mergeSessionPage(
|
|||
}
|
||||
|
||||
const incomingIds = new Set(incoming.map(session => session.id))
|
||||
// Deduplicate by compression lineage: when auto-compression rotates the tip
|
||||
// id (old #4 → new #5), the incoming page carries the new tip but the
|
||||
// previous list still holds the old one. Without lineage-level dedup both
|
||||
// rows survive as separate sidebar entries (fixes #43483).
|
||||
const incomingLineageKeys = new Set(
|
||||
incoming.map(session => session._lineage_root_id ?? session.id)
|
||||
)
|
||||
|
||||
const survivors = previous.filter(
|
||||
session =>
|
||||
!incomingIds.has(session.id) &&
|
||||
!incomingLineageKeys.has(session._lineage_root_id ?? session.id) &&
|
||||
(keep.has(session.id) || (session._lineage_root_id != null && keep.has(session._lineage_root_id)))
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue