From 6e5a0fa89364b1718b668fc3367382b58af98c00 Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 4 Jul 2026 14:15:31 +0800 Subject: [PATCH] fix(query): dedupe expanded search tail Avoid appending search hits that were already included as context when max message expansion limits only expand the first hits. --- apps/cli/src/query/commands-messages.test.ts | 19 +++++++++++++++++++ apps/cli/src/query/commands-messages.ts | 7 ++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/query/commands-messages.test.ts b/apps/cli/src/query/commands-messages.test.ts index c382f74f..d98d3601 100644 --- a/apps/cli/src/query/commands-messages.test.ts +++ b/apps/cli/src/query/commands-messages.test.ts @@ -2,6 +2,7 @@ import { describe, it } from 'node:test' import assert from 'node:assert/strict' import { + appendUniqueTailMessages, assertContextAnchorsPresent, capExpandedSearchMessages, parseContextIds, @@ -27,6 +28,24 @@ describe('capExpandedSearchMessages', () => { }) }) +describe('appendUniqueTailMessages', () => { + it('does not append tail hits already included by expanded context', () => { + const expanded: MessageLike[] = [ + { id: 10, senderName: 'Alice', content: 'alpha hit', timestamp: 1710000000 }, + { id: 11, senderName: 'Bob', content: 'beta hit as context', timestamp: 1710000001 }, + ] + const tail: MessageLike[] = [ + { id: 11, senderName: 'Bob', content: 'beta hit as context', timestamp: 1710000001 }, + { id: 12, senderName: 'Carol', content: 'tail hit', timestamp: 1710000002 }, + ] + + assert.deepEqual( + appendUniqueTailMessages(expanded, tail).map((message) => message.id), + [10, 11, 12] + ) + }) +}) + describe('parseContextIds', () => { it('rejects blank id tokens instead of converting them to 0', () => { assert.throws(() => parseContextIds('1021,'), /Invalid --id value/) diff --git a/apps/cli/src/query/commands-messages.ts b/apps/cli/src/query/commands-messages.ts index ff0a560e..5458fbb1 100644 --- a/apps/cli/src/query/commands-messages.ts +++ b/apps/cli/src/query/commands-messages.ts @@ -64,6 +64,11 @@ export function capExpandedSearchMessages( return capped.sort((a, b) => messages.indexOf(a) - messages.indexOf(b)) } +export function appendUniqueTailMessages(expanded: MessageLike[], tail: MessageLike[]): MessageLike[] { + const expandedIds = new Set(expanded.map((message) => message.id)) + return [...expanded, ...tail.filter((message) => !expandedIds.has(message.id))] +} + export function parseContextIds(value: string): number[] { const tokens = value.split(',').map((token) => token.trim()) const ids = tokens.map((token) => (token.length > 0 ? Number(token) : Number.NaN)) @@ -281,7 +286,7 @@ export function registerMessageCommands(program: Command): void { ) const keptHits = new Set(expandableHits.map((m) => m.id)) const tail = hits.filter((m) => !keptHits.has(m.id)) - pipelineMessages = [...expanded, ...tail] + pipelineMessages = appendUniqueTailMessages(expanded, tail) if (pipelineMessages.length > maxMessages) { pipelineMessages = capExpandedSearchMessages(pipelineMessages, hitIds, maxMessages) warnings.push(`response capped at ${maxMessages} messages`)