fix(core): join multi-block message text with a real newline (#1793)

extractTextFromContent joined text blocks with a literal "\\n" (backslash-n)
instead of a newline. Assistant messages commonly arrive as multi-block content
arrays, and the joined text feeds conversation-arc fact extraction whose regexes
deliberately treat newlines as boundaries (e.g. env-var and URL values use
[^\\s\\n"']+). With the literal separator a value at the end of one block
absorbed the next block — e.g. API_KEY=secret123 across two blocks recorded the
knowledge-graph value as "secret123\\nand..." instead of "secret123".

Use a real newline so blocks stay separated. Adds a regression test asserting
the extracted env-var value stops at the block boundary.
This commit is contained in:
Nik
2026-06-26 21:41:28 +08:00
committed by GitHub
parent 2083d1cdff
commit 4704cbc474
2 changed files with 28 additions and 1 deletions
+27
View File
@@ -161,6 +161,33 @@ describe('conversationArc', () => {
expect(getArc()?.currentPhase).toBe('implementing')
})
it('joins multi-block text with a real newline so fact extraction stops at block boundaries', async () => {
initializeArc()
const blockMessage = {
message: {
role: 'assistant',
content: [
{ type: 'text', text: 'Set export API_KEY=secret123' },
{ type: 'text', text: 'and then continue' },
],
id: 'test',
type: 'message',
created_at: Date.now(),
},
sender: 'assistant',
}
await updateArcPhase([blockMessage as any])
const graph = getGlobalGraph()
const envVar = Object.values(graph.entities).find(
(e: any) => e.type === 'environment_variable' && e.name === 'API_KEY',
)
expect(envVar).toBeDefined()
// With a literal "\n" separator the value absorbed the next block
// (`secret123\nand`); a real newline stops the value at the block boundary.
expect((envVar as any).attributes.value).toBe('secret123')
})
it('progresses phases forward only', async () => {
initializeArc()
await updateArcPhase([createMessage('user', 'Write code')])
+1 -1
View File
@@ -122,7 +122,7 @@ function extractTextFromContent(content: unknown): string {
return content
.filter((block: any) => block.type === 'text' && typeof block.text === 'string')
.map((block: any) => block.text)
.join('\\n')
.join('\n')
}
return ''
}