feat: add conversationTitle to context enhancement

Pass inbound group titles into <dsh_im_source> without extra platform API lookups.
DingTalk uses conversationTitle; Telegram groups use chat.title.
This commit is contained in:
曜 星 2026-09-01 17:15:17 +08:00
parent 13fc85cdce
commit 7a68e94f54
11 changed files with 82 additions and 30 deletions

View file

@ -177,7 +177,8 @@ function fixture(channel, { contextEnhancement, onAsk } = {}) {
const from = { id: actor === 'actor' ? 42 : 43, is_bot: false };
nameValue(from, 'first_name');
value = normalizeTelegramUpdate({ update_id: id, message: {
message_id: id, chat: { id: 100, type: group ? 'supergroup' : 'private' }, from,
message_id: id, chat: { id: 100, type: group ? 'supergroup' : 'private',
...(group ? { title: 'Telegram群' } : {}) }, from,
text: group ? `@testbot ${text}` : text, entities: group ? [{ type: 'mention', offset: 0, length: 8 }] : [],
} }, { botId: 'bot', username: 'testbot' });
} else if (channel === 'discord') {
@ -206,7 +207,8 @@ function fixture(channel, { contextEnhancement, onAsk } = {}) {
item_list: [{ type: 1, text_item: { text } }] };
} else if (channel === 'dingtalk') {
value = { msgId: String(id), msgtype: 'text', text: { content: text }, senderStaffId: actor,
conversationType: group ? '2' : '1', conversationId: 'chat', isInAtList: true,
conversationType: group ? '2' : '1', conversationId: 'chat',
conversationTitle: group ? '钉钉测试群' : undefined, isInAtList: true,
sessionWebhook: 'https://oapi.dingtalk.com/robot/reply?ticket=test' };
nameValue(value, 'senderNick');
} else if (channel === 'qq') {
@ -299,6 +301,8 @@ for (const channel of CHANNELS) {
senderId: channel === 'telegram' ? '42' : channel === 'whatsapp' ? 'actor@s.whatsapp.net' : 'actor',
...(['dingtalk', 'telegram', 'discord', 'whatsapp'].includes(channel) || (channel === 'qq' && kind === 'group')
? { senderName: 'Ada' } : {}),
...(channel === 'dingtalk' && kind === 'group' ? { conversationTitle: '钉钉测试群' } : {}),
...(channel === 'telegram' && kind === 'group' ? { conversationTitle: 'Telegram群' } : {}),
botId: `${channel}_internal`,
};
assert.deepEqual(sourceOf(current.prompts[0]), expected);

View file

@ -217,6 +217,11 @@ test('context settings default to off with sender ID and empty guidance, and exp
assert.equal(senderNameHelp.parent.props.className, 'dim-contextHelp dim-contextFieldHelp');
assert.equal(senderNameHelp.parent.parent.props.className, 'dim-contextFieldText');
assert.match(textOf(senderNameTooltip), /不是每个渠道.*dsh_im_source.*省略 senderName/s);
const conversationTitleHelp = renderer.root.findByProps({ 'aria-label': '查看群聊会话标题字段说明' });
const conversationTitleTooltip = renderer.root.findByProps({ id: conversationTitleHelp.props['aria-describedby'] });
assert.equal(conversationTitleHelp.props.type, 'button');
assert.equal(conversationTitleHelp.parent.props.className, 'dim-contextHelp dim-contextFieldHelp');
assert.match(textOf(conversationTitleTooltip), /不是每个渠道.*dsh_im_source.*省略 conversationTitle/s);
assert.deepEqual(saved, []);
});

View file

@ -185,16 +185,16 @@ test('group and direct messages use only their own fields and guidance', () => {
assert.doesNotMatch(direct, /GROUP-ONLY-TOKEN|channel/);
});
test('all 32 source-field subsets are projected in canonical order with no hidden fields', () => {
test('all 64 source-field subsets are projected in canonical order with no hidden fields', () => {
const expected = {
channel: 'telegram', conversationType: 'group',
senderId: '123', senderName: '张三', botId: 'bot_one',
senderId: '123', senderName: '张三', conversationTitle: '测试群', botId: 'bot_one',
};
for (let mask = 0; mask < 32; mask += 1) {
for (let mask = 0; mask < 64; mask += 1) {
const fields = CONTEXT_ENHANCEMENT_FIELDS.filter((_field, index) => mask & (1 << index));
const selected = snapshot(config({ fields: [...fields].reverse() }));
const actual = enhanceContextContent(' original\n', selected, () => ({
channel: 'telegram', senderId: 123, senderName: '张三',
channel: 'telegram', senderId: 123, senderName: '张三', conversationTitle: '测试群',
botId: 'platform-secret-not-used', conversationType: 'direct', token: 'never-sent',
}));
if (fields.length === 0) {