fix(status): pick latest assistant message in single pass (#1297)

* fix(status): pick latest assistant message in single pass

Sync reconciliation can splice messages out of array order, so the
previous "filter + sort + last" pipeline could pick the wrong record
as the latest assistant message and surface a stale status.

Replace it with a single-pass scan that compares created-time (tiebreak
by id) — same selection, but resilient to non-sorted message arrays.

* refactor: use comparator + running max for latest assistant pick

Address Greptile review: collapse the dual-exit-point single-pass max
finder (separate continue + isNewer branches, repeated null checks)
into a single isLater(a, b) comparator with one running-max comparison
per iteration. Same selection rule, easier to verify.

---------

Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com>
This commit is contained in:
vhqtvn
2026-05-18 18:17:18 +03:00
committed by GitHub
co-authored by vhqtvn
parent cbe6335ed9
commit 4f25c3009d
+20 -19
View File
@@ -198,28 +198,29 @@ export function useAssistantStatus(): AssistantStatusSnapshot {
return { activePartType: undefined, activeToolName: undefined, statusText: 'working', isGenericStatus: true };
}
const assistantMessages = sessionMessages
.filter(
(msg): msg is AssistantSessionMessageRecord =>
isAssistantMessage(msg.info) && !isFullySyntheticMessage(msg.parts)
);
// Pick the latest assistant message by created-time (tiebreak by id) in a
// single pass — sync reconciliation can splice messages out of array order.
const isLater = (a: AssistantSessionMessageRecord, b: AssistantSessionMessageRecord) => {
const aTime = typeof a.info.time?.created === 'number' ? a.info.time.created : null;
const bTime = typeof b.info.time?.created === 'number' ? b.info.time.created : null;
if (aTime !== null && bTime !== null && aTime !== bTime) return aTime > bTime;
return a.info.id.localeCompare(b.info.id) > 0;
};
if (assistantMessages.length === 0) {
return { activePartType: undefined, activeToolName: undefined, statusText: 'working', isGenericStatus: true };
let lastAssistant: AssistantSessionMessageRecord | null = null;
for (const candidate of sessionMessages) {
if (!isAssistantMessage(candidate.info) || isFullySyntheticMessage(candidate.parts)) {
continue;
}
const typed = candidate as AssistantSessionMessageRecord;
if (lastAssistant === null || isLater(typed, lastAssistant)) {
lastAssistant = typed;
}
}
const sortedAssistantMessages = [...assistantMessages].sort((a, b) => {
const aCreated = typeof a.info.time?.created === 'number' ? a.info.time.created : null;
const bCreated = typeof b.info.time?.created === 'number' ? b.info.time.created : null;
if (aCreated !== null && bCreated !== null && aCreated !== bCreated) {
return aCreated - bCreated;
}
return a.info.id.localeCompare(b.info.id);
});
const lastAssistant = sortedAssistantMessages[sortedAssistantMessages.length - 1];
if (!lastAssistant) {
return { activePartType: undefined, activeToolName: undefined, statusText: 'working', isGenericStatus: true };
}
let activePartType: 'text' | 'tool' | 'reasoning' | 'editing' | undefined = undefined;
let activeToolName: string | undefined = undefined;