Skip to content

Commit 3dac5a8

Browse files
committed
fix(chat): widen selection code fence so embedded backticks can't truncate it
Cursor: resolveFileSelectionResource wrapped the selected passage in a fixed ``` fence, so a selection that itself contained a fenced code block closed the outer fence early and the agent received a truncated snippet. The fence is now one backtick longer than the longest backtick run in the content (floored at three), matching CommonMark's close rule.
1 parent 5c4454b commit 3dac5a8

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

‎apps/sim/lib/copilot/chat/process-contents.test.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,35 @@ describe('processContextsServer - file_selection contexts', () => {
355355

356356
expect(result).toEqual([])
357357
})
358+
359+
it('widens the code fence so an embedded ``` block cannot close it early', async () => {
360+
getWorkspaceFile.mockResolvedValue({ name: 'readme.md', folderPath: null })
361+
362+
const snippet = 'before\n```ts\nconst x = 1\n```\nafter'
363+
const result = await processContextsServer(
364+
[
365+
{
366+
kind: 'file_selection',
367+
fileId: 'file-1',
368+
label: 'readme.md:1-5',
369+
text: snippet,
370+
startLine: 1,
371+
endLine: 5,
372+
} as ChatContext,
373+
],
374+
'user-1',
375+
'explain',
376+
'ws-1'
377+
)
378+
379+
const [ctx] = result
380+
// Outer fence must be longer than the embedded ``` run, and the full snippet
381+
// (including its inner fence) must survive intact.
382+
expect(ctx.content).toContain('````')
383+
expect(ctx.content).toContain(snippet)
384+
expect(ctx.content.startsWith('Selected passage')).toBe(true)
385+
expect(ctx.content.endsWith('````')).toBe(true)
386+
})
358387
})
359388

360389
describe('processContextsServer - table_selection contexts', () => {

‎apps/sim/lib/copilot/chat/process-contents.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,21 @@ async function resolveFileResource(
878878
}
879879
}
880880

881+
/**
882+
* Picks a backtick fence long enough to wrap `content` without an embedded
883+
* backtick run closing it early. Per CommonMark, a fenced block ends only on a
884+
* run of at least as many backticks as the opener, so the fence is one longer
885+
* than the longest run inside the content, floored at the standard three. Keeps
886+
* a selection that itself contains a ``` code block from truncating the snippet.
887+
*/
888+
function codeFenceFor(content: string): string {
889+
let longest = 0
890+
for (const match of content.matchAll(/`+/g)) {
891+
longest = Math.max(longest, match[0].length)
892+
}
893+
return '`'.repeat(Math.max(3, longest + 1))
894+
}
895+
881896
/**
882897
* Resolves a highlighted passage from a file into an inline, citable snippet.
883898
* The selected text travels with the request (it is the user's own content), so
@@ -902,7 +917,8 @@ async function resolveFileSelectionResource(
902917
: startLine
903918
? ` (line ${startLine})`
904919
: ''
905-
const content = `Selected passage from ${record.name}${lineRange}:\n\n\`\`\`\n${snippet}\n\`\`\``
920+
const fence = codeFenceFor(snippet)
921+
const content = `Selected passage from ${record.name}${lineRange}:\n\n${fence}\n${snippet}\n${fence}`
906922
return {
907923
type: 'file_selection',
908924
tag: label ? `@${label}` : '@',

0 commit comments

Comments
 (0)