diff --git a/apps/desktop/src/lib/chat-messages.test.ts b/apps/desktop/src/lib/chat-messages.test.ts index 6ddabbe9b38..d6bfd77525a 100644 --- a/apps/desktop/src/lib/chat-messages.test.ts +++ b/apps/desktop/src/lib/chat-messages.test.ts @@ -220,6 +220,19 @@ describe('toChatMessages', () => { expect(chatMessageText(message)).toBe('@file:foo.ts\n\nlook') }) + it('leaves an inline @ ref in place instead of hoisting a duplicate', () => { + const [message] = toChatMessages([ + { + role: 'user', + content: + 'summarize @file:`src/main.ts` for me\n\n--- Attached Context ---\n\nšŸ“„ @file:`src/main.ts` (10 tokens)\n```ts\nconst x = 1\n```', + timestamp: 1 + } + ]) + + expect(chatMessageText(message)).toBe('summarize @file:`src/main.ts` for me') + }) + it('projects durable timeline kinds without inspecting their text', () => { const messages = toChatMessages([ { role: 'user', content: 'real user turn', timestamp: 1 }, diff --git a/apps/desktop/src/lib/chat-messages.ts b/apps/desktop/src/lib/chat-messages.ts index f0d8e2b7c64..13cca5b511b 100644 --- a/apps/desktop/src/lib/chat-messages.ts +++ b/apps/desktop/src/lib/chat-messages.ts @@ -311,7 +311,12 @@ function displayContentForMessage(role: SessionMessage['role'], content: unknown const attachedContext = textContent.slice(marker.index + marker[0].length) const refs = [...new Set(Array.from(attachedContext.matchAll(CONTEXT_REF_RE)).map(match => match[0]))] - return [refs.join('\n'), visibleText].filter(Boolean).join('\n\n') || visibleText + // The prose keeps the `@file:` token the user typed, so it already chips in + // place. Only hoist a ref the prose is missing — a turn persisted by an older + // backend that stripped the tokens. Re-listing an inline ref would chip twice. + const missing = refs.filter(ref => !visibleText.includes(ref)) + + return [missing.join('\n'), visibleText].filter(Boolean).join('\n\n') || visibleText } function transcriptContent(displayKind: SessionMessage['display_kind'], content: string): string | null {