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

@ -1122,6 +1122,10 @@ var EN = Object.freeze({
"\u67E5\u770B\u7FA4\u804A\u53D1\u9001\u8005\u6635\u79F0\u5B57\u6BB5\u8BF4\u660E": "View group sender name field details",
"\u67E5\u770B\u79C1\u804A\u53D1\u9001\u8005\u6635\u79F0\u5B57\u6BB5\u8BF4\u660E": "View direct sender name field details",
"\u8BE5\u5B57\u6BB5\u4E0D\u662F\u6BCF\u4E2A\u6E20\u9053\u90FD\u80FD\u63D0\u4F9B\u3002\u5F53\u524D\u6D88\u606F\u6CA1\u6709\u53D1\u9001\u8005\u6635\u79F0\u65F6\uFF0C\u5373\u4F7F\u5DF2\u9009\u62E9\u8BE5\u5B57\u6BB5\uFF0C<dsh_im_source> \u4E2D\u4E5F\u4F1A\u7701\u7565 senderName\u3002": "This field is not available on every channel. If the current message has no sender name, <dsh_im_source> omits senderName even when the field is selected.",
"\u4F1A\u8BDD\u6807\u9898": "Conversation title",
"\u67E5\u770B\u7FA4\u804A\u4F1A\u8BDD\u6807\u9898\u5B57\u6BB5\u8BF4\u660E": "View group conversation title field details",
"\u67E5\u770B\u79C1\u804A\u4F1A\u8BDD\u6807\u9898\u5B57\u6BB5\u8BF4\u660E": "View direct conversation title field details",
"\u8BE5\u5B57\u6BB5\u4E0D\u662F\u6BCF\u4E2A\u6E20\u9053\u90FD\u80FD\u63D0\u4F9B\u3002\u9489\u9489\u7FA4\u804A\u4F1A\u5E26\u4E0A\u7FA4\u540D\u3002\u5F53\u524D\u6D88\u606F\u6CA1\u6709\u4F1A\u8BDD\u6807\u9898\u65F6\uFF0C\u5373\u4F7F\u5DF2\u9009\u62E9\u8BE5\u5B57\u6BB5\uFF0C<dsh_im_source> \u4E2D\u4E5F\u4F1A\u7701\u7565 conversationTitle\u3002": "This field is not available on every channel. DingTalk group chats include the group name. If the current message has no conversation title, <dsh_im_source> omits conversationTitle even when the field is selected.",
"\u673A\u5668\u4EBA\u6807\u8BC6": "Bot ID",
"\u589E\u5F3A\u63D0\u793A\u8BCD": "Guidance",
"\u67E5\u770B\u589E\u5F3A\u63D0\u793A\u8BCD\u4F7F\u7528\u8BF4\u660E": "View guidance instructions",
@ -1149,7 +1153,7 @@ var EN = Object.freeze({
"\u4E0A\u4E0B\u6587\u589E\u5F3A\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002": "Could not save context enhancement. Try again.",
"\u8BF7\u63D0\u4EA4\u5B8C\u6574\u7684\u4E0A\u4E0B\u6587\u589E\u5F3A\u8BBE\u7F6E\u3002": "Submit the complete context enhancement settings.",
"\u7FA4\u804A\u548C\u79C1\u804A\u5F00\u5173\u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002": "Group and direct switches must be booleans.",
"\u6765\u6E90\u5B57\u6BB5\u53EA\u80FD\u9009\u62E9\u5DF2\u5B9A\u4E49\u7684\u4E94\u4E2A\u5B57\u6BB5\u3002": "Source fields must be chosen from the five defined fields.",
"\u6765\u6E90\u5B57\u6BB5\u53EA\u80FD\u9009\u62E9\u5DF2\u5B9A\u4E49\u7684\u516D\u4E2A\u5B57\u6BB5\u3002": "Source fields must be chosen from the six defined fields.",
"\u4FDD\u5B58\u4E2D\u2026": "Saving\u2026",
"\u672A\u8BBE\u7F6E": "Not set",
"\u5DE5\u4F5C\u533A\u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002": "Could not update the workspace. Try again.",
@ -2075,6 +2079,7 @@ var CONTEXT_ENHANCEMENT_FIELDS = Object.freeze([
"conversationType",
"senderId",
"senderName",
"conversationTitle",
"botId"
]);
var CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH = 8e3;
@ -2114,7 +2119,7 @@ function validateContextEnhancementScope(input) {
throw invalidConfig("\u7FA4\u804A\u548C\u79C1\u804A\u5F00\u5173\u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002");
}
if (!Array.isArray(fields) || ![...fields].every((field) => CONTEXT_ENHANCEMENT_FIELDS.includes(field))) {
throw invalidConfig("\u6765\u6E90\u5B57\u6BB5\u53EA\u80FD\u9009\u62E9\u5DF2\u5B9A\u4E49\u7684\u4E94\u4E2A\u5B57\u6BB5\u3002");
throw invalidConfig("\u6765\u6E90\u5B57\u6BB5\u53EA\u80FD\u9009\u62E9\u5DF2\u5B9A\u4E49\u7684\u516D\u4E2A\u5B57\u6BB5\u3002");
}
if (typeof guidance !== "string" || guidance.length > CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH) {
throw invalidConfig(`\u589E\u5F3A\u63D0\u793A\u8BCD\u4E0D\u5F97\u8D85\u8FC7 ${CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH} \u4E2A\u5B57\u7B26\u3002`);
@ -2895,14 +2900,26 @@ var FIELD_LABELS = Object.freeze({
conversationType: "\u4F1A\u8BDD\u7C7B\u578B",
senderId: "\u53D1\u9001\u8005\u6807\u8BC6",
senderName: "\u53D1\u9001\u8005\u6635\u79F0",
conversationTitle: "\u4F1A\u8BDD\u6807\u9898",
botId: "\u673A\u5668\u4EBA\u6807\u8BC6"
});
var FIELD_HELP = Object.freeze({
senderName: Object.freeze({
labelKey: "senderNameHelpLabel",
text: "\u8BE5\u5B57\u6BB5\u4E0D\u662F\u6BCF\u4E2A\u6E20\u9053\u90FD\u80FD\u63D0\u4F9B\u3002\u5F53\u524D\u6D88\u606F\u6CA1\u6709\u53D1\u9001\u8005\u6635\u79F0\u65F6\uFF0C\u5373\u4F7F\u5DF2\u9009\u62E9\u8BE5\u5B57\u6BB5\uFF0C<dsh_im_source> \u4E2D\u4E5F\u4F1A\u7701\u7565 senderName\u3002"
}),
conversationTitle: Object.freeze({
labelKey: "conversationTitleHelpLabel",
text: "\u8BE5\u5B57\u6BB5\u4E0D\u662F\u6BCF\u4E2A\u6E20\u9053\u90FD\u80FD\u63D0\u4F9B\u3002\u9489\u9489\u7FA4\u804A\u4F1A\u5E26\u4E0A\u7FA4\u540D\u3002\u5F53\u524D\u6D88\u606F\u6CA1\u6709\u4F1A\u8BDD\u6807\u9898\u65F6\uFF0C\u5373\u4F7F\u5DF2\u9009\u62E9\u8BE5\u5B57\u6BB5\uFF0C<dsh_im_source> \u4E2D\u4E5F\u4F1A\u7701\u7565 conversationTitle\u3002"
})
});
var SCOPE_COPY = Object.freeze({
group: Object.freeze({
title: "\u7FA4\u804A",
enable: "\u542F\u7528",
fieldsHelpLabel: "\u67E5\u770B\u7FA4\u804A\u6765\u6E90\u5B57\u6BB5\u8BF4\u660E",
senderNameHelpLabel: "\u67E5\u770B\u7FA4\u804A\u53D1\u9001\u8005\u6635\u79F0\u5B57\u6BB5\u8BF4\u660E",
conversationTitleHelpLabel: "\u67E5\u770B\u7FA4\u804A\u4F1A\u8BDD\u6807\u9898\u5B57\u6BB5\u8BF4\u660E",
guidanceLabel: "\u589E\u5F3A\u63D0\u793A\u8BCD",
guidanceHelpLabel: "\u67E5\u770B\u7FA4\u804A\u589E\u5F3A\u63D0\u793A\u8BCD\u4F7F\u7528\u8BF4\u660E",
guidanceUsage: "\u7528\u4E8E\u544A\u8BC9\u6A21\u578B\u5982\u4F55\u4F7F\u7528\u5F53\u524D\u7FA4\u804A\u6D88\u606F\u7684 <dsh_im_source> \u6765\u6E90\u5B57\u6BB5\u3002\u53EA\u586B\u5199\u6B63\u6587\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u6DFB\u52A0 <dsh_im_source_guidance> \u6210\u5BF9\u6807\u7B7E\u3002",
@ -2913,6 +2930,7 @@ var SCOPE_COPY = Object.freeze({
enable: "\u542F\u7528",
fieldsHelpLabel: "\u67E5\u770B\u79C1\u804A\u6765\u6E90\u5B57\u6BB5\u8BF4\u660E",
senderNameHelpLabel: "\u67E5\u770B\u79C1\u804A\u53D1\u9001\u8005\u6635\u79F0\u5B57\u6BB5\u8BF4\u660E",
conversationTitleHelpLabel: "\u67E5\u770B\u79C1\u804A\u4F1A\u8BDD\u6807\u9898\u5B57\u6BB5\u8BF4\u660E",
guidanceLabel: "\u589E\u5F3A\u63D0\u793A\u8BCD",
guidanceHelpLabel: "\u67E5\u770B\u79C1\u804A\u589E\u5F3A\u63D0\u793A\u8BCD\u4F7F\u7528\u8BF4\u660E",
guidanceUsage: "\u7528\u4E8E\u544A\u8BC9\u6A21\u578B\u5982\u4F55\u4F7F\u7528\u5F53\u524D\u79C1\u804A\u6D88\u606F\u7684 <dsh_im_source> \u6765\u6E90\u5B57\u6BB5\u3002\u53EA\u586B\u5199\u6B63\u6587\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u6DFB\u52A0 <dsh_im_source_guidance> \u6210\u5BF9\u6807\u7B7E\u3002",
@ -2953,7 +2971,6 @@ function ContextEnhancementScopeEditor({
const copy = SCOPE_COPY[kind];
const unavailableId = `${idPrefix}-${kind}-unavailable`;
const fieldsHelpId = `${idPrefix}-${kind}-fields-help`;
const senderNameHelpId = `${idPrefix}-${kind}-sender-name-help`;
const guidanceId = `${idPrefix}-${kind}-guidance`;
const guidanceHelpId = `${idPrefix}-${kind}-guidance-help`;
const disabled = busy || !supported;
@ -3030,21 +3047,21 @@ function ContextEnhancementScopeEditor({
"span",
{ className: "dim-contextFieldText" },
h2("label", { className: "dim-contextFieldName", htmlFor: fieldId }, FIELD_LABELS[field]),
field === "senderName" ? h2(
FIELD_HELP[field] ? h2(
"span",
{ className: "dim-contextHelp dim-contextFieldHelp" },
h2("button", {
type: "button",
className: "dim-contextHelpButton dim-contextFieldHelpButton",
disabled,
"aria-label": copy.senderNameHelpLabel,
"aria-describedby": senderNameHelpId
"aria-label": copy[FIELD_HELP[field].labelKey],
"aria-describedby": `${idPrefix}-${kind}-${field}-help`
}, h2("span", { "aria-hidden": "true" }, "?")),
h2("span", {
id: senderNameHelpId,
id: `${idPrefix}-${kind}-${field}-help`,
className: "dim-contextTooltip dim-contextFieldTooltip",
role: "tooltip"
}, "\u8BE5\u5B57\u6BB5\u4E0D\u662F\u6BCF\u4E2A\u6E20\u9053\u90FD\u80FD\u63D0\u4F9B\u3002\u5F53\u524D\u6D88\u606F\u6CA1\u6709\u53D1\u9001\u8005\u6635\u79F0\u65F6\uFF0C\u5373\u4F7F\u5DF2\u9009\u62E9\u8BE5\u5B57\u6BB5\uFF0C<dsh_im_source> \u4E2D\u4E5F\u4F1A\u7701\u7565 senderName\u3002")
}, FIELD_HELP[field].text)
) : null,
h2("label", { className: "dim-contextFieldKey", htmlFor: fieldId }, field)
)

File diff suppressed because one or more lines are too long

View file

@ -16,15 +16,28 @@ const FIELD_LABELS = Object.freeze({
conversationType: '会话类型',
senderId: '发送者标识',
senderName: '发送者昵称',
conversationTitle: '会话标题',
botId: '机器人标识',
});
const FIELD_HELP = Object.freeze({
senderName: Object.freeze({
labelKey: 'senderNameHelpLabel',
text: '该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。',
}),
conversationTitle: Object.freeze({
labelKey: 'conversationTitleHelpLabel',
text: '该字段不是每个渠道都能提供。钉钉群聊会带上群名。当前消息没有会话标题时,即使已选择该字段,<dsh_im_source> 中也会省略 conversationTitle。',
}),
});
const SCOPE_COPY = Object.freeze({
group: Object.freeze({
title: '群聊',
enable: '启用',
fieldsHelpLabel: '查看群聊来源字段说明',
senderNameHelpLabel: '查看群聊发送者昵称字段说明',
conversationTitleHelpLabel: '查看群聊会话标题字段说明',
guidanceLabel: '增强提示词',
guidanceHelpLabel: '查看群聊增强提示词使用说明',
guidanceUsage: '用于告诉模型如何使用当前群聊消息的 <dsh_im_source> 来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。',
@ -35,6 +48,7 @@ const SCOPE_COPY = Object.freeze({
enable: '启用',
fieldsHelpLabel: '查看私聊来源字段说明',
senderNameHelpLabel: '查看私聊发送者昵称字段说明',
conversationTitleHelpLabel: '查看私聊会话标题字段说明',
guidanceLabel: '增强提示词',
guidanceHelpLabel: '查看私聊增强提示词使用说明',
guidanceUsage: '用于告诉模型如何使用当前私聊消息的 <dsh_im_source> 来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。',
@ -73,7 +87,6 @@ function ContextEnhancementScopeEditor({
const copy = SCOPE_COPY[kind];
const unavailableId = `${idPrefix}-${kind}-unavailable`;
const fieldsHelpId = `${idPrefix}-${kind}-fields-help`;
const senderNameHelpId = `${idPrefix}-${kind}-sender-name-help`;
const guidanceId = `${idPrefix}-${kind}-guidance`;
const guidanceHelpId = `${idPrefix}-${kind}-guidance-help`;
const disabled = busy || !supported;
@ -118,16 +131,17 @@ function ContextEnhancementScopeEditor({
}),
h('span', { className: 'dim-contextFieldText' },
h('label', { className: 'dim-contextFieldName', htmlFor: fieldId }, FIELD_LABELS[field]),
field === 'senderName' ? h('span', { className: 'dim-contextHelp dim-contextFieldHelp' },
FIELD_HELP[field] ? h('span', { className: 'dim-contextHelp dim-contextFieldHelp' },
h('button', {
type: 'button', className: 'dim-contextHelpButton dim-contextFieldHelpButton', disabled,
'aria-label': copy.senderNameHelpLabel, 'aria-describedby': senderNameHelpId,
'aria-label': copy[FIELD_HELP[field].labelKey],
'aria-describedby': `${idPrefix}-${kind}-${field}-help`,
}, h('span', { 'aria-hidden': 'true' }, '?')),
h('span', {
id: senderNameHelpId,
id: `${idPrefix}-${kind}-${field}-help`,
className: 'dim-contextTooltip dim-contextFieldTooltip',
role: 'tooltip',
}, '该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。')) : null,
}, FIELD_HELP[field].text)) : null,
h('label', { className: 'dim-contextFieldKey', htmlFor: fieldId }, field)));
}))),
h('div', { className: 'dim-contextGuidance dim-contextScopeBlock' },

View file

@ -288,6 +288,10 @@ const EN = Object.freeze({
'查看群聊发送者昵称字段说明': 'View group sender name field details',
'查看私聊发送者昵称字段说明': 'View direct sender name field details',
'该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。': 'This field is not available on every channel. If the current message has no sender name, <dsh_im_source> omits senderName even when the field is selected.',
'会话标题': 'Conversation title',
'查看群聊会话标题字段说明': 'View group conversation title field details',
'查看私聊会话标题字段说明': 'View direct conversation title field details',
'该字段不是每个渠道都能提供。钉钉群聊会带上群名。当前消息没有会话标题时,即使已选择该字段,<dsh_im_source> 中也会省略 conversationTitle。': 'This field is not available on every channel. DingTalk group chats include the group name. If the current message has no conversation title, <dsh_im_source> omits conversationTitle even when the field is selected.',
'机器人标识': 'Bot ID',
'增强提示词': 'Guidance',
'查看增强提示词使用说明': 'View guidance instructions',
@ -315,7 +319,7 @@ const EN = Object.freeze({
'上下文增强保存失败,请重试。': 'Could not save context enhancement. Try again.',
'请提交完整的上下文增强设置。': 'Submit the complete context enhancement settings.',
'群聊和私聊开关必须是布尔值。': 'Group and direct switches must be booleans.',
'来源字段只能选择已定义的五个字段。': 'Source fields must be chosen from the five defined fields.',
'来源字段只能选择已定义的六个字段。': 'Source fields must be chosen from the six defined fields.',
'保存中…': 'Saving…',
'未设置': 'Not set',
'工作区修改失败,请重试。': 'Could not update the workspace. Try again.',

View file

@ -1001,6 +1001,7 @@ export class DingtalkHarnessBridge {
channel: 'dingtalk',
senderId: sender,
senderName: message.senderNick,
conversationTitle: message.conversationTitle,
}));
}
if (typeof this.#api.createAiCard === 'function'

View file

@ -1,6 +1,6 @@
// Shared by the Host and settings UI; keep this module browser-compatible.
export const CONTEXT_ENHANCEMENT_FIELDS = Object.freeze([
'channel', 'conversationType', 'senderId', 'senderName', 'botId',
'channel', 'conversationType', 'senderId', 'senderName', 'conversationTitle', 'botId',
]);
export const CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH = 8_000;
@ -36,7 +36,10 @@ const CHANNELS = new Set([
'wecom', 'weixin', 'feishu', 'dingtalk', 'qq',
'slack', 'telegram', 'discord', 'whatsapp',
]);
const SOURCE_LIMITS = { channel: 16, conversationType: 6, senderId: 256, senderName: 256, botId: 128 };
const SOURCE_LIMITS = {
channel: 16, conversationType: 6, senderId: 256, senderName: 256,
conversationTitle: 256, botId: 128,
};
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;
function invalidConfig(message) {
@ -61,7 +64,7 @@ function validateContextEnhancementScope(input) {
throw invalidConfig('群聊和私聊开关必须是布尔值。');
}
if (!Array.isArray(fields) || ![...fields].every((field) => CONTEXT_ENHANCEMENT_FIELDS.includes(field))) {
throw invalidConfig('来源字段只能选择已定义的五个字段。');
throw invalidConfig('来源字段只能选择已定义的六个字段。');
}
if (typeof guidance !== 'string' || guidance.length > CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH) {
throw invalidConfig(`增强提示词不得超过 ${CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH} 个字符。`);
@ -146,7 +149,9 @@ function sourceString(value, field) {
function sourceBlock(snapshot, sourceFactory) {
const { fields } = snapshot.config;
const needsSource = fields.some((field) => ['channel', 'senderId', 'senderName'].includes(field));
const needsSource = fields.some((field) => [
'channel', 'senderId', 'senderName', 'conversationTitle',
].includes(field));
const source = needsSource ? sourceFactory?.() : null;
const projected = {};
for (const field of fields) {

View file

@ -678,6 +678,7 @@ export class TextHarnessBridge {
channel: this.#descriptor.key,
senderId,
senderName: message.contextSource?.()?.senderName,
conversationTitle: message.contextSource?.()?.conversationTitle,
}));
}
const { answer, artifacts = [] } = await askInWorkspaceSession({

View file

@ -166,6 +166,7 @@ export function normalizeTelegramUpdate(update, {
senderName: [message.from?.first_name, message.from?.last_name]
.filter((value) => typeof value === 'string' && value.trim())
.map((value) => value.trim()).join(' ') || message.from?.username,
conversationTitle: direct ? undefined : message.chat?.title,
}),
senderIsBot: message.from?.is_bot === true,
kind: direct ? 'direct' : 'group',

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) {