fix(desktop): keep pre-compression transcript reachable after in-place /compress (#105256)

This commit is contained in:
finn763
2026-09-09 20:49:52 +08:00
committed by brooklyn!
parent cb75168adf
commit b236dca1b6
2 changed files with 60 additions and 1 deletions

View File

@@ -776,6 +776,56 @@ describe('usePromptActions /compress', () => {
expect(renderedText).toContain('sure, here is the summary')
})
it('keeps pre-compression messages reachable after the transcript rewrite (#105256)', async () => {
const seeds: Record<string, unknown>[] = []
const requestGateway = vi.fn(async (method: string, _params?: Record<string, unknown>, _timeoutMs?: number) => {
if (method === 'session.compress') {
return {
removed: 2,
summary: { headline: 'Compressed: 4 → 2 messages' },
messages: [
{ role: 'user', content: 'summarized context' },
{ role: 'assistant', content: 'sure, here is the summary' }
]
} as never
}
return {} as never
})
let handle: HarnessHandle | null = null
await actRender(
<Harness
onReady={h => (handle = h)}
onSeedState={s => seeds.push(s)}
refreshSessions={async () => undefined}
requestGateway={requestGateway}
seedMessages={[
{ id: 'pre-1', role: 'user', parts: [textPart('pre-compression user turn')], timestamp: 0 },
{ id: 'pre-2', role: 'assistant', parts: [textPart('pre-compression assistant reply')], timestamp: 1 }
]}
/>
)
await handle!.submitText('/compress')
// The transcript was replaced with the post-compress history.
const finalMessages = seeds[seeds.length - 1]?.messages as Array<{ parts?: Array<{ text?: string }> }>
const renderedText = (finalMessages ?? [])
.flatMap(message => (message.parts ?? []).map(part => part.text ?? ''))
.join('\n')
expect(renderedText).toContain('summarized context')
expect(renderedText).toContain('sure, here is the summary')
// Active-session scrollback: the pre-compression segment stays in the
// transcript so scrolling up still reaches it.
expect(renderedText).toContain('pre-compression user turn')
expect(renderedText).toContain('pre-compression assistant reply')
expect(renderedText.indexOf('pre-compression user turn')).toBeLessThan(renderedText.indexOf('summarized context'))
})
it('uses the compute-host response transcript and success output', async () => {
const seeds: Record<string, unknown>[] = []

View File

@@ -2,6 +2,7 @@ import { skillInvocationText } from '@hermes/shared'
import { parseCommandDispatch, parseSlashCommand } from '@hermes/shared'
import { type MutableRefObject, useCallback, useRef } from 'react'
import { mergeOlderTranscriptPage } from '@/app/chat/transcript-backfill'
import { prepareDefaultNewSession } from '@/app/session/new-session-route'
import { invalidateContextBreakdown } from '@/app/shell/hooks/use-context-breakdown'
import { getProfiles } from '@/hermes'
@@ -763,10 +764,18 @@ export function useSlashCommand(deps: SlashCommandDeps) {
// toChatMessages handles it directly. updateSessionState only
// publishes for the active runtime, guarding against a late result
// clobbering the foreground after a session switch.
// The pre-compression segment stays reachable in the active
// session (#105256): the rewrite shares no anchor with the live
// transcript, so grafting would drop everything before the
// summary. Prepend it instead (deduped by row/message id), keeping
// the post-compress history authoritative for overlapping rows.
if (Array.isArray(result?.messages)) {
updateSessionState(
sessionId,
state => ({ ...state, messages: toChatMessages(result.messages!) }),
state => ({
...state,
messages: mergeOlderTranscriptPage(toChatMessages(result.messages!), state.messages)
}),
storedSessionId
)
}