From fd9c52ae1e31aa97839ae7f9062ae91a87bddade Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:52:35 +0530 Subject: [PATCH] refactor(desktop): name the tail entry's route and owner TranscriptTailState.profile was documented as the owning profile but now holds the request ROUTE (the owner is the map key). Rename the recordTranscriptTail parameters to route/owner, fix the field doc, say why a bare-string scope still defaults to 'default' while an object scope does not, and correct transcript-tail-cache's claim that it mirrors the in-memory key (it keys by request scope and the two stores never cross-read). --- apps/desktop/src/api/sessions.ts | 10 ++++----- .../src/store/transcript-tail-cache.ts | 6 ++++-- apps/desktop/src/store/transcript-tail.ts | 21 +++++++++++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/api/sessions.ts b/apps/desktop/src/api/sessions.ts index 339449db11..002284af8c 100644 --- a/apps/desktop/src/api/sessions.ts +++ b/apps/desktop/src/api/sessions.ts @@ -459,9 +459,9 @@ export function getLatestSessionMessages( // (ambient, profile string, or explicit pin). Otherwise refreshes create // duplicate tail entries and "Show earlier" cannot resolve the loaded tail. // Capture before awaiting: the active gateway may change during the read. - const tailScope = { ...connectionScoped(), ...sessionScoped(profile) } + const route = { ...connectionScoped(), ...sessionScoped(profile) } // Normalize only the lookup key; backfill must replay the original route. - const ownerScope = { ...tailScope, connectionId: tailScope.connectionId || ambientOwnerConnectionId() } + const owner = { ...route, connectionId: route.connectionId || ambientOwnerConnectionId() } // includeCompacted: durable display history must include rows preserved by // in-place compaction (active=0, compacted=1); without them the transcript @@ -484,11 +484,11 @@ export function getLatestSessionMessages( // untagged read there lands on the ambient profile (`'default'` when // none is active). `null` is a custom HERMES_HOME and stays distinct. const servedProfile = page.profile === undefined ? getApiRequestProfile() || 'default' : page.profile - const resolvedOwnerScope = { ...ownerScope, profile: ownerScope.profile || servedProfile } - recordTranscriptTail(id, page, tailScope, resolvedOwnerScope) + const resolvedOwner = { ...owner, profile: owner.profile || servedProfile } + recordTranscriptTail(id, page, route, resolvedOwner) if (page.session_id && page.session_id !== id) { - recordTranscriptTail(page.session_id, page, tailScope, resolvedOwnerScope) + recordTranscriptTail(page.session_id, page, route, resolvedOwner) } return page diff --git a/apps/desktop/src/store/transcript-tail-cache.ts b/apps/desktop/src/store/transcript-tail-cache.ts index 04f13898a5..65619d19d2 100644 --- a/apps/desktop/src/store/transcript-tail-cache.ts +++ b/apps/desktop/src/store/transcript-tail-cache.ts @@ -23,8 +23,10 @@ import { withUniqueToolCallIdsWithinMessage } from '@/lib/chat-messages' // profile switches in the same window: an unscoped key let profile A's // tail be painted against profile B's backend, which then retried a // session id that does not exist there ("session not found") on every -// wake. The scope mirrors the in-memory twin's transcriptTailKey -// (transcript-tail.ts). Entries persisted before scoping shipped (v1, +// wake. The scope is the REQUEST scope the resume path passes (always a +// named profile); the in-memory twin (transcript-tail.ts) keys by the +// resolved owner instead and the two are never cross-read. Entries +// persisted before scoping shipped (v1, // bare-id keys) carry no owner and are unreachable by construction — // they are swept once per window so a stale tail can never paint again. diff --git a/apps/desktop/src/store/transcript-tail.ts b/apps/desktop/src/store/transcript-tail.ts index fec325dcdb..9e2443d2f9 100644 --- a/apps/desktop/src/store/transcript-tail.ts +++ b/apps/desktop/src/store/transcript-tail.ts @@ -23,8 +23,10 @@ export interface TranscriptTailState { /** The last hydration page was exactly the page limit, so older rows * likely exist beyond what the in-memory store holds. */ possiblyTruncated: boolean - /** Owning profile captured at hydration time, so a later backfill routes - * its REST read to the same backend that served the tail. */ + /** The request route captured at hydration time, replayed verbatim by a + * later backfill so it reaches the backend that served the tail. The + * resolved OWNER is the map key, not this field: an ambient read must stay + * ambient even once the server has named its profile. */ profile?: TranscriptProfileScope } @@ -43,6 +45,8 @@ let transcriptTailOrder: string[] = [] type TailPage = Pick function normalizedScope(profile?: TranscriptProfileScope): { connectionId: string; profile: string } | null { + // A bare string is the legacy "named profile" spelling: it always names a + // profile, so empty means the default one. if (typeof profile === 'string') { return { connectionId: '', profile: profile.trim() || 'default' } } @@ -115,21 +119,20 @@ function setTranscriptTailEntry(key: string, state: TranscriptTailState): void { $transcriptTailBySessionId.set(next) } -/** Record the outcome of a tail hydration (`getLatestSessionMessages`). */ +/** Record the outcome of a tail hydration (`getLatestSessionMessages`). + * `route` is what the request was sent with; `owner` is the resolved backend + * the entry is keyed under (defaults to the route when they coincide). */ export function recordTranscriptTail( storedSessionId: string, page: TailPage, - profile?: TranscriptProfileScope, - ownerScope: TranscriptProfileScope | undefined = profile + route?: TranscriptProfileScope, + owner: TranscriptProfileScope | undefined = route ): void { if (!storedSessionId) { return } - // Keep request routing separate from the resolved owner used for lookup: - // backfill must replay an ambient read even when the server names its profile. - const key = transcriptTailKey(storedSessionId, ownerScope) - setTranscriptTailEntry(key, tailStateFromPage(page, profile)) + setTranscriptTailEntry(transcriptTailKey(storedSessionId, owner), tailStateFromPage(page, route)) } /** Advance the bookkeeping after one older backfill page landed. */