feat: add reply/quoted message context support

Add generic reply context fields to NewMessage (reply_to_message_id,
reply_to_message_content, reply_to_sender_name) so any channel can
pass quoted message context to the agent.

- Add thread_id and reply_to_* fields to NewMessage interface
- Add DB migration for reply context columns on messages table
- Update storeMessage/getMessagesSince/getNewMessages to persist and
  retrieve reply fields
- Render reply context as <quoted_message> XML in formatMessages
- Add DB and formatting tests

Co-Authored-By: Alfred-the-buttler <leon.alfred.bot@gmail.com>
Co-Authored-By: moktamd <moktamd@users.noreply.github.com>
Co-Authored-By: gurixs-carson <gurixs-carson@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
exe.dev user
2026-04-02 17:05:24 +00:00
parent 7b337a7a07
commit ee599b9f0c
5 changed files with 171 additions and 4 deletions

View File

@@ -115,6 +115,62 @@ describe('formatMessages', () => {
expect(result).toContain('<messages>\n\n</messages>');
});
it('renders reply context as quoted_message element', () => {
const result = formatMessages(
[
makeMsg({
content: 'Yes, on my way!',
reply_to_message_id: '42',
reply_to_message_content: 'Are you coming tonight?',
reply_to_sender_name: 'Bob',
}),
],
TZ,
);
expect(result).toContain('reply_to="42"');
expect(result).toContain(
'<quoted_message from="Bob">Are you coming tonight?</quoted_message>',
);
expect(result).toContain('Yes, on my way!</message>');
});
it('omits reply attributes when no reply context', () => {
const result = formatMessages([makeMsg()], TZ);
expect(result).not.toContain('reply_to');
expect(result).not.toContain('quoted_message');
});
it('omits quoted_message when content is missing but id is present', () => {
const result = formatMessages(
[
makeMsg({
reply_to_message_id: '42',
reply_to_sender_name: 'Bob',
}),
],
TZ,
);
expect(result).toContain('reply_to="42"');
expect(result).not.toContain('quoted_message');
});
it('escapes special characters in reply context', () => {
const result = formatMessages(
[
makeMsg({
reply_to_message_id: '1',
reply_to_message_content: '<script>alert("xss")</script>',
reply_to_sender_name: 'A & B',
}),
],
TZ,
);
expect(result).toContain('from="A &amp; B"');
expect(result).toContain(
'&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;',
);
});
it('converts timestamps to local time for given timezone', () => {
// 2024-01-01T18:30:00Z in America/New_York (EST) = 1:30 PM
const result = formatMessages(