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

@ -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',