@xmanrui/dsh-im 4.7.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +43 -6
- package/lib/index.js +145 -145
- package/package.json +5 -1
- package/plugin-src/client/context-enhancement.js +14 -0
- package/plugin-src/client/i18n.js +9 -1
- package/plugin-src/client/workspace-directory-picker.js +14 -3
- package/src/channels/dingtalk/dingtalk-bridge.mjs +1 -0
- package/src/channels/feishu/bridge.mjs +2 -0
- package/src/channels/qq/qq-bridge.mjs +1 -0
- package/src/channels/shared/context-enhancement.mjs +5 -4
- package/src/channels/shared/text-harness-bridge.mjs +5 -2
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +2 -0
- package/src/channels/wecom/wecom-bridge.mjs +1 -0
- package/src/channels/weixin/weixin-bridge.mjs +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
{
|
|
35
35
|
"name": "divingleee",
|
|
36
36
|
"url": "https://github.com/divingleee"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Chan-0312",
|
|
40
|
+
"url": "https://github.com/Chan-0312"
|
|
37
41
|
}
|
|
38
42
|
],
|
|
39
43
|
"license": "MIT",
|
|
@@ -17,6 +17,8 @@ const FIELD_LABELS = Object.freeze({
|
|
|
17
17
|
senderId: '发送者标识',
|
|
18
18
|
senderName: '发送者昵称',
|
|
19
19
|
conversationTitle: '会话标题',
|
|
20
|
+
chatId: '会话标识',
|
|
21
|
+
threadId: '话题标识',
|
|
20
22
|
botId: '机器人标识',
|
|
21
23
|
});
|
|
22
24
|
|
|
@@ -29,6 +31,14 @@ const FIELD_HELP = Object.freeze({
|
|
|
29
31
|
labelKey: 'conversationTitleHelpLabel',
|
|
30
32
|
text: '该字段不是每个渠道都能提供。钉钉群聊会带上群名。当前消息没有会话标题时,即使已选择该字段,<dsh_im_source> 中也会省略 conversationTitle。',
|
|
31
33
|
}),
|
|
34
|
+
chatId: Object.freeze({
|
|
35
|
+
labelKey: 'chatIdHelpLabel',
|
|
36
|
+
text: '该字段不是每个渠道都能提供。会话标识用于区分不同的群组或私聊,飞书群聊会带上群 ID。当前消息没有会话标识时,即使已选择该字段,<dsh_im_source> 中也会省略 chatId。',
|
|
37
|
+
}),
|
|
38
|
+
threadId: Object.freeze({
|
|
39
|
+
labelKey: 'threadIdHelpLabel',
|
|
40
|
+
text: '该字段不是每个渠道都能提供。飞书话题群的消息会带上话题 ID,用于区分同一群组内的不同话题;当前消息不在话题中时,即使已选择该字段,<dsh_im_source> 中也会省略 threadId。',
|
|
41
|
+
}),
|
|
32
42
|
});
|
|
33
43
|
|
|
34
44
|
const SCOPE_COPY = Object.freeze({
|
|
@@ -38,6 +48,8 @@ const SCOPE_COPY = Object.freeze({
|
|
|
38
48
|
fieldsHelpLabel: '查看群聊来源字段说明',
|
|
39
49
|
senderNameHelpLabel: '查看群聊发送者昵称字段说明',
|
|
40
50
|
conversationTitleHelpLabel: '查看群聊会话标题字段说明',
|
|
51
|
+
chatIdHelpLabel: '查看群聊会话标识字段说明',
|
|
52
|
+
threadIdHelpLabel: '查看群聊话题标识字段说明',
|
|
41
53
|
guidanceLabel: '增强提示词',
|
|
42
54
|
guidanceHelpLabel: '查看群聊增强提示词使用说明',
|
|
43
55
|
guidanceUsage: '用于告诉模型如何使用当前群聊消息的 <dsh_im_source> 来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。',
|
|
@@ -49,6 +61,8 @@ const SCOPE_COPY = Object.freeze({
|
|
|
49
61
|
fieldsHelpLabel: '查看私聊来源字段说明',
|
|
50
62
|
senderNameHelpLabel: '查看私聊发送者昵称字段说明',
|
|
51
63
|
conversationTitleHelpLabel: '查看私聊会话标题字段说明',
|
|
64
|
+
chatIdHelpLabel: '查看私聊会话标识字段说明',
|
|
65
|
+
threadIdHelpLabel: '查看私聊话题标识字段说明',
|
|
52
66
|
guidanceLabel: '增强提示词',
|
|
53
67
|
guidanceHelpLabel: '查看私聊增强提示词使用说明',
|
|
54
68
|
guidanceUsage: '用于告诉模型如何使用当前私聊消息的 <dsh_im_source> 来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。',
|
|
@@ -292,6 +292,14 @@ const EN = Object.freeze({
|
|
|
292
292
|
'查看群聊会话标题字段说明': 'View group conversation title field details',
|
|
293
293
|
'查看私聊会话标题字段说明': 'View direct conversation title field details',
|
|
294
294
|
'该字段不是每个渠道都能提供。钉钉群聊会带上群名。当前消息没有会话标题时,即使已选择该字段,<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.',
|
|
295
|
+
'会话标识': 'Chat ID',
|
|
296
|
+
'查看群聊会话标识字段说明': 'View group chat ID field details',
|
|
297
|
+
'查看私聊会话标识字段说明': 'View direct chat ID field details',
|
|
298
|
+
'该字段不是每个渠道都能提供。会话标识用于区分不同的群组或私聊,飞书群聊会带上群 ID。当前消息没有会话标识时,即使已选择该字段,<dsh_im_source> 中也会省略 chatId。': 'This field is not available on every channel. The chat ID distinguishes different groups or direct chats, and Feishu group chats include the group ID. If the current message has no chat ID, <dsh_im_source> omits chatId even when the field is selected.',
|
|
299
|
+
'话题标识': 'Topic ID',
|
|
300
|
+
'查看群聊话题标识字段说明': 'View group topic ID field details',
|
|
301
|
+
'查看私聊话题标识字段说明': 'View direct topic ID field details',
|
|
302
|
+
'该字段不是每个渠道都能提供。飞书话题群的消息会带上话题 ID,用于区分同一群组内的不同话题;当前消息不在话题中时,即使已选择该字段,<dsh_im_source> 中也会省略 threadId。': 'This field is not available on every channel. Messages in a Feishu topic chat carry the topic ID, which distinguishes different topics inside the same group. If the current message is not inside a topic, <dsh_im_source> omits threadId even when the field is selected.',
|
|
295
303
|
'机器人标识': 'Bot ID',
|
|
296
304
|
'增强提示词': 'Guidance',
|
|
297
305
|
'查看增强提示词使用说明': 'View guidance instructions',
|
|
@@ -319,7 +327,7 @@ const EN = Object.freeze({
|
|
|
319
327
|
'上下文增强保存失败,请重试。': 'Could not save context enhancement. Try again.',
|
|
320
328
|
'请提交完整的上下文增强设置。': 'Submit the complete context enhancement settings.',
|
|
321
329
|
'群聊和私聊开关必须是布尔值。': 'Group and direct switches must be booleans.',
|
|
322
|
-
'
|
|
330
|
+
'来源字段只能选择已定义的八个字段。': 'Source fields must be chosen from the eight defined fields.',
|
|
323
331
|
'保存中…': 'Saving…',
|
|
324
332
|
'未设置': 'Not set',
|
|
325
333
|
'工作区修改失败,请重试。': 'Could not update the workspace. Try again.',
|
|
@@ -7,6 +7,17 @@ function pickerErrorCode(error) {
|
|
|
7
7
|
return error?.rpcError?.code ?? error?.code;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const PICKER_ERROR_KINDS = new Map([
|
|
11
|
+
['directory-picker/unavailable', 'unavailable'],
|
|
12
|
+
['directory-picker-unavailable', 'unavailable'],
|
|
13
|
+
['directory-picker/unreadable', 'unreadable'],
|
|
14
|
+
['directory-unreadable', 'unreadable'],
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function pickerErrorKind(error) {
|
|
18
|
+
return PICKER_ERROR_KINDS.get(pickerErrorCode(error));
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
function pickerErrorDetails(error) {
|
|
11
22
|
return error?.rpcError?.details ?? error?.details;
|
|
12
23
|
}
|
|
@@ -110,9 +121,9 @@ export function WorkspaceDirectoryPicker({
|
|
|
110
121
|
const initialPath = initialPathRef.current;
|
|
111
122
|
const initial = await loadDirectory(initialPath || undefined, { reportError: false });
|
|
112
123
|
if (!active || initial.aborted || initial.value) return;
|
|
113
|
-
const
|
|
124
|
+
const kind = pickerErrorKind(initial.error);
|
|
114
125
|
const details = pickerErrorDetails(initial.error);
|
|
115
|
-
if (
|
|
126
|
+
if (kind === 'unavailable'
|
|
116
127
|
&& details?.capability === 'native'
|
|
117
128
|
&& typeof picker.pickDirectory === 'function') {
|
|
118
129
|
setLoading(true);
|
|
@@ -128,7 +139,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
128
139
|
}
|
|
129
140
|
return;
|
|
130
141
|
}
|
|
131
|
-
if (initialPath &&
|
|
142
|
+
if (initialPath && kind === 'unreadable') {
|
|
132
143
|
const home = await loadDirectory(undefined, { reportError: false });
|
|
133
144
|
if (!active || home.aborted || home.value) return;
|
|
134
145
|
setError(pickerErrorMessage(home.error));
|
|
@@ -1136,6 +1136,7 @@ export class DingtalkHarnessBridge {
|
|
|
1136
1136
|
senderId: sender,
|
|
1137
1137
|
senderName: message.senderNick,
|
|
1138
1138
|
conversationTitle: message.conversationTitle,
|
|
1139
|
+
chatId: message.conversationId,
|
|
1139
1140
|
}));
|
|
1140
1141
|
contextEnhanced = content !== originalContent;
|
|
1141
1142
|
}
|
|
@@ -3330,6 +3330,8 @@ export class FeishuHarnessBridge {
|
|
|
3330
3330
|
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
3331
3331
|
channel: 'feishu',
|
|
3332
3332
|
senderId: senderOpenId(event),
|
|
3333
|
+
chatId: event.message.chat_id,
|
|
3334
|
+
threadId: event.message.thread_id,
|
|
3333
3335
|
}));
|
|
3334
3336
|
contextEnhanced = content !== originalContent;
|
|
3335
3337
|
}
|
|
@@ -890,6 +890,7 @@ export class QqHarnessBridge {
|
|
|
890
890
|
channel: 'qq',
|
|
891
891
|
senderId: sender,
|
|
892
892
|
senderName: message.kind === 'group' ? message.senderName : undefined,
|
|
893
|
+
chatId: message.kind === 'group' ? message.groupOpenid : message.senderId,
|
|
893
894
|
}));
|
|
894
895
|
contextEnhanced = content !== originalContent;
|
|
895
896
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Shared by the Host and settings UI; keep this module browser-compatible.
|
|
2
2
|
export const CONTEXT_ENHANCEMENT_FIELDS = Object.freeze([
|
|
3
|
-
'channel', 'conversationType', 'senderId', 'senderName', 'conversationTitle',
|
|
3
|
+
'channel', 'conversationType', 'senderId', 'senderName', 'conversationTitle',
|
|
4
|
+
'chatId', 'threadId', 'botId',
|
|
4
5
|
]);
|
|
5
6
|
|
|
6
7
|
export const CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH = 8_000;
|
|
@@ -38,7 +39,7 @@ const CHANNELS = new Set([
|
|
|
38
39
|
]);
|
|
39
40
|
const SOURCE_LIMITS = {
|
|
40
41
|
channel: 16, conversationType: 6, senderId: 256, senderName: 256,
|
|
41
|
-
conversationTitle: 256, botId: 128,
|
|
42
|
+
conversationTitle: 256, chatId: 256, threadId: 256, botId: 128,
|
|
42
43
|
};
|
|
43
44
|
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;
|
|
44
45
|
|
|
@@ -64,7 +65,7 @@ function validateContextEnhancementScope(input) {
|
|
|
64
65
|
throw invalidConfig('群聊和私聊开关必须是布尔值。');
|
|
65
66
|
}
|
|
66
67
|
if (!Array.isArray(fields) || ![...fields].every((field) => CONTEXT_ENHANCEMENT_FIELDS.includes(field))) {
|
|
67
|
-
throw invalidConfig('
|
|
68
|
+
throw invalidConfig('来源字段只能选择已定义的八个字段。');
|
|
68
69
|
}
|
|
69
70
|
if (typeof guidance !== 'string' || guidance.length > CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH) {
|
|
70
71
|
throw invalidConfig(`增强提示词不得超过 ${CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH} 个字符。`);
|
|
@@ -150,7 +151,7 @@ function sourceString(value, field) {
|
|
|
150
151
|
function sourceBlock(snapshot, sourceFactory) {
|
|
151
152
|
const { fields } = snapshot.config;
|
|
152
153
|
const needsSource = fields.some((field) => [
|
|
153
|
-
'channel', 'senderId', 'senderName', 'conversationTitle',
|
|
154
|
+
'channel', 'senderId', 'senderName', 'conversationTitle', 'chatId', 'threadId',
|
|
154
155
|
].includes(field));
|
|
155
156
|
const source = needsSource ? sourceFactory?.() : null;
|
|
156
157
|
const projected = {};
|
|
@@ -683,11 +683,14 @@ export class TextHarnessBridge {
|
|
|
683
683
|
let contextEnhanced = false;
|
|
684
684
|
if (snapshot) {
|
|
685
685
|
const originalContent = content ?? text;
|
|
686
|
+
const contextSource = message.contextSource?.();
|
|
686
687
|
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
687
688
|
channel: this.#descriptor.key,
|
|
688
689
|
senderId,
|
|
689
|
-
senderName:
|
|
690
|
-
conversationTitle:
|
|
690
|
+
senderName: contextSource?.senderName,
|
|
691
|
+
conversationTitle: contextSource?.conversationTitle,
|
|
692
|
+
chatId: contextSource?.chatId ?? message.conversationId,
|
|
693
|
+
threadId: contextSource?.threadId,
|
|
691
694
|
}));
|
|
692
695
|
contextEnhanced = content !== originalContent;
|
|
693
696
|
}
|
|
@@ -172,6 +172,10 @@ export function normalizeSlackEvent(payload, botUserId, {
|
|
|
172
172
|
senderIsBot: String(event.user) === String(botUserId),
|
|
173
173
|
kind: direct ? 'direct' : 'group',
|
|
174
174
|
conversationId: direct ? String(event.channel) : `${event.channel}:${threadTs}`,
|
|
175
|
+
contextSource: () => ({
|
|
176
|
+
chatId: String(event.channel),
|
|
177
|
+
threadId: event.thread_ts ? String(event.thread_ts) : undefined,
|
|
178
|
+
}),
|
|
175
179
|
content: stripBotMention(event.text ?? '', botUserId),
|
|
176
180
|
plainText: !Array.isArray(event.files) || event.files.length === 0,
|
|
177
181
|
images: Array.isArray(event.files)
|
|
@@ -266,6 +266,8 @@ export function normalizeTelegramUpdate(update, {
|
|
|
266
266
|
.filter((value) => typeof value === 'string' && value.trim())
|
|
267
267
|
.map((value) => value.trim()).join(' ') || message.from?.username,
|
|
268
268
|
conversationTitle: direct ? undefined : message.chat?.title,
|
|
269
|
+
chatId: String(chatId),
|
|
270
|
+
threadId: messageThreadId === undefined ? undefined : String(messageThreadId),
|
|
269
271
|
}),
|
|
270
272
|
senderIsBot: message.from?.is_bot === true,
|
|
271
273
|
kind: direct ? 'direct' : 'group',
|