Skip to content

Commit 36b88ca

Browse files
Merge branch 'main' into cursor/cve/next-b42c
2 parents b6649bf + b65db72 commit 36b88ca

6 files changed

Lines changed: 74 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Upgraded `hono` to `^4.12.18` to address CVE-2026-44455, CVE-2026-44456, CVE-2026-44457, CVE-2026-44458. [#1186](https://github.com/sourcebot-dev/sourcebot/pull/1186)
2020
- Upgraded `ip-address` to `^10.2.0` to address CVE-2026-42338. [#1189](https://github.com/sourcebot-dev/sourcebot/pull/1189)
2121
- Upgraded `fast-xml-builder` to `^1.2.0` to address CVE-2026-44664, CVE-2026-44665. [#1184](https://github.com/sourcebot-dev/sourcebot/pull/1184)
22-
- Upgraded `next` to `^16.2.6` to address CVE-2026-45109. [#1203](https://github.com/sourcebot-dev/sourcebot/pull/1203)
22+
- Fixed file citations from the `get_diff` tool not being reliably citable in chat answers. [#1205](https://github.com/sourcebot-dev/sourcebot/pull/1205)
23+
- - Upgraded `next` to `^16.2.6` to address CVE-2026-45109. [#1203](https://github.com/sourcebot-dev/sourcebot/pull/1203)
2324

2425
### Changed
2526
- Reduced the log verbosity of the worker by changing various log messages from info to debug. [#1179](https://github.com/sourcebot-dev/sourcebot/pull/1179)

packages/web/src/features/chat/components/chatThread/answerCard.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import useCaptureEvent from "@/hooks/useCaptureEvent";
1818
import { LangfuseWeb } from "langfuse";
1919
import { env } from "@sourcebot/shared/client";
2020
import isEqual from "fast-deep-equal/react";
21+
import { FileSource } from "../../types";
2122

2223
interface AnswerCardProps {
2324
answerText: string;
2425
messageId: string;
2526
chatId: string;
2627
traceId?: string;
28+
sources: FileSource[];
2729
}
2830

2931
const langfuseWeb = env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY ? new LangfuseWeb({
@@ -36,6 +38,7 @@ const AnswerCardComponent = forwardRef<HTMLDivElement, AnswerCardProps>(({
3638
messageId,
3739
chatId,
3840
traceId,
41+
sources,
3942
}, forwardedRef) => {
4043
const markdownRendererRef = useRef<HTMLDivElement>(null);
4144
// eslint-disable-next-line react-hooks/refs -- ref.current is passed to a custom hook, not used directly in render output
@@ -53,14 +56,14 @@ const AnswerCardComponent = forwardRef<HTMLDivElement, AnswerCardProps>(({
5356

5457
const onCopyAnswer = useCallback(() => {
5558
const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
56-
const markdownText = convertLLMOutputToPortableMarkdown(answerText, baseUrl);
59+
const markdownText = convertLLMOutputToPortableMarkdown(answerText, baseUrl, sources);
5760
navigator.clipboard.writeText(markdownText);
5861
toast({
5962
description: "✅ Copied to clipboard",
6063
});
6164
captureEvent('wa_chat_copy_answer_pressed', { chatId });
6265
return true;
63-
}, [answerText, chatId, captureEvent, toast]);
66+
}, [answerText, sources, chatId, captureEvent, toast]);
6467

6568
const onFeedback = useCallback(async (feedbackType: 'like' | 'dislike') => {
6669
setIsSubmittingFeedback(true);

packages/web/src/features/chat/components/chatThread/chatThreadListItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
371371
chatId={chatId}
372372
messageId={assistantMessage.id}
373373
traceId={assistantMessage.metadata?.traceId}
374+
sources={referencedFileSources}
374375
/>
375376
) : !isStreaming && (
376377
<p className="text-destructive">Error: No answer response was provided</p>

packages/web/src/features/chat/utils.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,23 @@ export const createFileReference = ({ repo, path, startLine, endLine }: { repo:
244244
* Markdown format. Practically, this means converting references into Markdown
245245
* links and removing the answer tag.
246246
*/
247-
export const convertLLMOutputToPortableMarkdown = (text: string, baseUrl: string): string => {
247+
export const convertLLMOutputToPortableMarkdown = (text: string, baseUrl: string, sources: FileSource[]): string => {
248248
return text
249249
.replace(ANSWER_TAG, '')
250250
.replace(FILE_REFERENCE_REGEX, (_, repo, fileName, startLine, endLine) => {
251-
const displayName = fileName.split('/').pop() || fileName;
251+
const reference = createFileReference({
252+
repo,
253+
path: fileName,
254+
startLine,
255+
endLine
256+
});
257+
258+
const source = tryResolveFileReference(reference, sources);
259+
if (!source) {
260+
return fileName;
261+
}
252262

253-
let linkText = displayName;
263+
let linkText = source.name;
254264
if (startLine) {
255265
if (endLine && startLine !== endLine) {
256266
linkText += `:${startLine}-${endLine}`;
@@ -268,6 +278,7 @@ export const convertLLMOutputToPortableMarkdown = (text: string, baseUrl: string
268278
// Construct full browse URL
269279
const browsePath = getBrowsePath({
270280
repoName: repo,
281+
revisionName: source.revision,
271282
path: fileName,
272283
pathType: 'blob',
273284
highlightRange,

packages/web/src/features/mcp/askCodebase.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,15 @@ export const askCodebase = (params: AskCodebaseParams): Promise<AskCodebaseResul
196196
: undefined;
197197
const answerText = answerPart?.text ?? '';
198198

199+
const fileSources = finalMessages.flatMap((message) =>
200+
message.parts
201+
.filter((part) => part.type === 'data-source')
202+
.map((part) => part.data)
203+
.filter((source) => source.type === 'file')
204+
);
205+
199206
const baseUrl = env.AUTH_URL;
200-
const portableAnswer = convertLLMOutputToPortableMarkdown(answerText, baseUrl);
207+
const portableAnswer = convertLLMOutputToPortableMarkdown(answerText, baseUrl, fileSources);
201208
const chatUrl = `${baseUrl}/chat/${chat.id}`;
202209

203210
logger.debug(`Completed blocking agent for chat ${chat.id}`, { chatId: chat.id });

packages/web/src/features/tools/getDiff.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { isServiceError } from '@/lib/utils';
44
import description from './getDiff.txt';
55
import { formatDiffAsGitDiff } from './utils';
66
import { logger } from './logger';
7-
import { ToolDefinition } from './types';
7+
import { Source, ToolDefinition } from './types';
88
import { CodeHostType } from '@sourcebot/db';
99
import { getRepoInfoByName } from '@/actions';
1010

@@ -49,6 +49,48 @@ export const getDiffDefinition: ToolDefinition<'get_diff', typeof getDiffRequest
4949

5050
const gitDiffOutput = formatDiffAsGitDiff(response);
5151

52+
const sources: Source[] = response.files.flatMap((file) => {
53+
// Deleted: only the base side exists.
54+
if (file.newPath === null) {
55+
return file.oldPath ? [{
56+
type: 'file' as const,
57+
repo: repoInfo.name,
58+
path: file.oldPath,
59+
name: file.oldPath.split('/').pop() ?? file.oldPath,
60+
revision: base,
61+
}] : [];
62+
}
63+
64+
// Renamed: emit both sides since they have distinct paths.
65+
if (file.oldPath && file.oldPath !== file.newPath) {
66+
return [
67+
{
68+
type: 'file' as const,
69+
repo: repoInfo.name,
70+
path: file.oldPath,
71+
name: file.oldPath.split('/').pop() ?? file.oldPath,
72+
revision: base,
73+
},
74+
{
75+
type: 'file' as const,
76+
repo: repoInfo.name,
77+
path: file.newPath,
78+
name: file.newPath.split('/').pop() ?? file.newPath,
79+
revision: head,
80+
},
81+
];
82+
}
83+
84+
// Added or modified: only the head side is citable.
85+
return [{
86+
type: 'file' as const,
87+
repo: repoInfo.name,
88+
path: file.newPath,
89+
name: file.newPath.split('/').pop() ?? file.newPath,
90+
revision: head,
91+
}];
92+
});
93+
5294
return {
5395
output: gitDiffOutput,
5496
metadata: {
@@ -58,6 +100,7 @@ export const getDiffDefinition: ToolDefinition<'get_diff', typeof getDiffRequest
58100
base,
59101
head,
60102
},
103+
sources,
61104
};
62105
},
63106
};

0 commit comments

Comments
 (0)