@xmanrui/dsh-im 0.12.0 → 0.13.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 +43 -37
- package/README.md +45 -39
- package/lib/client.js +2 -2
- package/lib/index.js +113 -112
- package/package.json +1 -1
- package/plugin-src/client/i18n.js +1 -1
- package/plugin-src/client/index.js +1 -1
- package/scripts/verify-package.mjs +34 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +3 -2
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +3 -2
- package/src/channels/qq/qq-bridge.mjs +3 -2
- package/src/channels/shared/model-command.mjs +44 -6
- package/src/channels/shared/text-harness-bridge.mjs +3 -2
- package/src/channels/wecom/wecom-bridge.mjs +3 -2
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "通过扫码或机器人凭据把IM机器人接入DeepSeek Harness(支持飞书、微信、钉钉、企业微信、QQ、Slack、Telegram、Discord和WhatsApp)。 Connect IM bots to DeepSeek Harness via QR code or credentials (9 channels).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -7,7 +7,7 @@ const EN = Object.freeze({
|
|
|
7
7
|
'IM机器人': 'IM bots',
|
|
8
8
|
'IM机器人设置': 'IM bot settings',
|
|
9
9
|
'IM 渠道': 'IM channels',
|
|
10
|
-
'
|
|
10
|
+
'让 DeepSeek Harness 触手可及': 'DeepSeek Harness, always within reach',
|
|
11
11
|
'帮助与反馈 · 前往 GitHub': 'Help & feedback · Open GitHub',
|
|
12
12
|
'微信': 'WeChat',
|
|
13
13
|
'飞书': 'Feishu',
|
|
@@ -139,7 +139,7 @@ export function IMSettingsTab({
|
|
|
139
139
|
width: 48,
|
|
140
140
|
height: 48,
|
|
141
141
|
}),
|
|
142
|
-
h('p', null, '
|
|
142
|
+
h('p', null, '让 DeepSeek Harness 触手可及')),
|
|
143
143
|
h('span', { className: 'dim-githubAction' },
|
|
144
144
|
h('a', {
|
|
145
145
|
className: 'dim-githubLink',
|
|
@@ -48,6 +48,40 @@ const [client, host, patch, manifestText, lockText, hostSource, clientSource, ex
|
|
|
48
48
|
const manifest = JSON.parse(manifestText);
|
|
49
49
|
const lock = JSON.parse(lockText);
|
|
50
50
|
|
|
51
|
+
// DSH runtime packages use module-local Symbol keys, so a second physical copy breaks Host lookup.
|
|
52
|
+
const forbiddenDshDependency = /^@deepseek-ai\/dsh-/;
|
|
53
|
+
const dependencySections = [
|
|
54
|
+
'dependencies',
|
|
55
|
+
'devDependencies',
|
|
56
|
+
'optionalDependencies',
|
|
57
|
+
'peerDependencies',
|
|
58
|
+
];
|
|
59
|
+
for (const section of dependencySections) {
|
|
60
|
+
for (const name of Object.keys(manifest[section] ?? {})) {
|
|
61
|
+
if (forbiddenDshDependency.test(name)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${name} must not be declared in ${section}; DSH runtime packages must come from the host`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const bundledDependencies = manifest.bundleDependencies ?? manifest.bundledDependencies ?? [];
|
|
69
|
+
if (Array.isArray(bundledDependencies)) {
|
|
70
|
+
for (const name of bundledDependencies) {
|
|
71
|
+
if (forbiddenDshDependency.test(name)) {
|
|
72
|
+
throw new Error(`${name} must not be bundled; DSH runtime packages must come from the host`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const forbiddenDshLockPaths = Object.keys(lock.packages ?? {}).filter((path) => (
|
|
77
|
+
/(?:^|\/)node_modules\/@deepseek-ai\/dsh-[^/]+(?:\/|$)/.test(path)
|
|
78
|
+
));
|
|
79
|
+
if (forbiddenDshLockPaths.length > 0) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`package lock must not install DSH runtime packages: ${forbiddenDshLockPaths.join(', ')}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
if (!client.includes('id: "@xmanrui/dsh-im"')) {
|
|
52
86
|
throw new Error('client bundle does not register the dsh-im loader id');
|
|
53
87
|
}
|
|
@@ -41,8 +41,9 @@ const HELP_TEXT = [
|
|
|
41
41
|
'/workspacelist 列出工作区绝对路径',
|
|
42
42
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
43
43
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
44
|
-
'/models
|
|
45
|
-
'/model [
|
|
44
|
+
'/models 按序号列出所有可用模型',
|
|
45
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
46
|
+
'示例:先发 /models,再发 /model 2',
|
|
46
47
|
'/stop 停止当前任务',
|
|
47
48
|
'/steer 补充指令 纠偏当前任务',
|
|
48
49
|
'/status 检查连接状态',
|
|
@@ -108,7 +108,7 @@ export class DiscordApi {
|
|
|
108
108
|
headers: {
|
|
109
109
|
authorization: `Bot ${this.#token}`,
|
|
110
110
|
'content-type': 'application/json',
|
|
111
|
-
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.
|
|
111
|
+
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.13.0)',
|
|
112
112
|
},
|
|
113
113
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
114
114
|
signal: requestSignal(signal, timeoutMs),
|
|
@@ -43,8 +43,9 @@ const HELP_TEXT = [
|
|
|
43
43
|
'/workspacelist 列出工作区绝对路径',
|
|
44
44
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
45
45
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
46
|
-
'/models
|
|
47
|
-
'/model [
|
|
46
|
+
'/models 按序号列出所有可用模型',
|
|
47
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
48
|
+
'示例:先发 /models,再发 /model 2',
|
|
48
49
|
'/stop 停止当前任务',
|
|
49
50
|
'/steer 补充指令 纠偏当前任务',
|
|
50
51
|
'/status 检查连接状态',
|
|
@@ -46,8 +46,9 @@ const HELP_TEXT = [
|
|
|
46
46
|
'/workspacelist 列出工作区绝对路径',
|
|
47
47
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
48
48
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
49
|
-
'/models
|
|
50
|
-
'/model [
|
|
49
|
+
'/models 按序号列出所有可用模型',
|
|
50
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
51
|
+
'示例:先发 /models,再发 /model 2',
|
|
51
52
|
'/stop 停止当前任务',
|
|
52
53
|
'/steer 补充指令 纠偏当前任务',
|
|
53
54
|
'/status 检查连接状态',
|
|
@@ -4,7 +4,7 @@ import { withSessionBindingLock } from './session-binding-lock.mjs';
|
|
|
4
4
|
|
|
5
5
|
const MODEL_COMMAND = /^\/model(?=$|\s)/i;
|
|
6
6
|
const MODELS_COMMAND = /^\/models(?=$|\s)/i;
|
|
7
|
-
const MODEL_USAGE = '用法:/model <provider>/<model>';
|
|
7
|
+
const MODEL_USAGE = '用法:/model <序号> 或 /model <provider>/<model>';
|
|
8
8
|
const MODELS_USAGE = '用法:/models(不带参数)';
|
|
9
9
|
const SESSION_BINDING_CHANGED = 'session-binding-changed';
|
|
10
10
|
const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
|
|
@@ -88,17 +88,46 @@ function matchingModel(catalog, requested) {
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function modelAt(catalog, requestedIndex) {
|
|
92
|
+
let index = 0;
|
|
93
|
+
for (const group of catalog.groups) {
|
|
94
|
+
for (const model of group.models) {
|
|
95
|
+
index += 1;
|
|
96
|
+
if (index === requestedIndex) {
|
|
97
|
+
return { provider: group.id, model: model.id };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function modelNumberRequest(requested) {
|
|
105
|
+
if (!/^\d+$/u.test(requested)) return null;
|
|
106
|
+
const index = Number(requested);
|
|
107
|
+
return { index: Number.isSafeInteger(index) && index > 0 ? index : null };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function invalidModelNumberMessage(requested) {
|
|
111
|
+
return [
|
|
112
|
+
`模型序号无效:${safeDisplayText(requested)}`,
|
|
113
|
+
'',
|
|
114
|
+
'请发送 /models 查看并输入有效的正整数序号。',
|
|
115
|
+
].join('\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
function formatCatalog(catalog) {
|
|
92
119
|
const currentId = catalog.current
|
|
93
120
|
? modelId(catalog.current.provider, catalog.current.model)
|
|
94
121
|
: null;
|
|
95
122
|
const lines = ['可用模型:'];
|
|
123
|
+
let index = 0;
|
|
96
124
|
if (catalog.groups.length === 0) lines.push('', '当前没有可用模型。');
|
|
97
125
|
for (const group of catalog.groups) {
|
|
98
126
|
lines.push('', safeDisplayText(group.name) || safeDisplayText(group.id));
|
|
99
127
|
for (const model of group.models) {
|
|
128
|
+
index += 1;
|
|
100
129
|
const id = modelId(group.id, model.id);
|
|
101
|
-
lines.push(
|
|
130
|
+
lines.push(`${index}. ${safeDisplayText(id)}${id === currentId ? '(当前)' : ''}`);
|
|
102
131
|
}
|
|
103
132
|
}
|
|
104
133
|
if (catalog.failures.length > 0) {
|
|
@@ -107,6 +136,7 @@ function formatCatalog(catalog) {
|
|
|
107
136
|
lines.push(`- ${safeDisplayText(failure.name) || safeDisplayText(failure.id)}`);
|
|
108
137
|
}
|
|
109
138
|
}
|
|
139
|
+
if (index > 0) lines.push('', '切换模型:/model <序号>');
|
|
110
140
|
return lines.join('\n');
|
|
111
141
|
}
|
|
112
142
|
|
|
@@ -116,7 +146,7 @@ function currentModelMessage(current) {
|
|
|
116
146
|
modelId(current.provider, current.model),
|
|
117
147
|
'',
|
|
118
148
|
'查看全部模型:/models',
|
|
119
|
-
'切换模型:/model
|
|
149
|
+
'切换模型:/model <序号>',
|
|
120
150
|
].join('\n');
|
|
121
151
|
}
|
|
122
152
|
|
|
@@ -125,7 +155,7 @@ function noSessionMessage() {
|
|
|
125
155
|
'当前聊天还没有会话。',
|
|
126
156
|
'',
|
|
127
157
|
'查看模型:/models',
|
|
128
|
-
'选择模型:/model
|
|
158
|
+
'选择模型:/model <序号>',
|
|
129
159
|
].join('\n');
|
|
130
160
|
}
|
|
131
161
|
|
|
@@ -243,7 +273,12 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
|
|
|
243
273
|
return commandResult(modelErrorMessage(error, 'select'));
|
|
244
274
|
}
|
|
245
275
|
}
|
|
246
|
-
|
|
276
|
+
const numberRequest = modelNumberRequest(requested);
|
|
277
|
+
if (numberRequest?.index === null) {
|
|
278
|
+
return commandResult(invalidModelNumberMessage(requested));
|
|
279
|
+
}
|
|
280
|
+
if (!numberRequest
|
|
281
|
+
&& (!requested.includes('/') || requested.startsWith('/') || requested.endsWith('/'))) {
|
|
247
282
|
return commandResult(MODEL_USAGE);
|
|
248
283
|
}
|
|
249
284
|
if (options.pendingInteraction) {
|
|
@@ -264,8 +299,11 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
|
|
|
264
299
|
const catalog = bound
|
|
265
300
|
? await sessionCatalog(bound.session, requestOptions)
|
|
266
301
|
: await listCatalog(harness, requestOptions);
|
|
267
|
-
const selection =
|
|
302
|
+
const selection = numberRequest
|
|
303
|
+
? modelAt(catalog, numberRequest.index)
|
|
304
|
+
: matchingModel(catalog, requested);
|
|
268
305
|
if (!selection) {
|
|
306
|
+
if (numberRequest) return commandResult(invalidModelNumberMessage(requested));
|
|
269
307
|
return commandResult([
|
|
270
308
|
`没有找到模型:${safeDisplayText(requested)}`,
|
|
271
309
|
'',
|
|
@@ -316,8 +316,9 @@ export class TextHarnessBridge {
|
|
|
316
316
|
'/workspacelist 列出工作区绝对路径',
|
|
317
317
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
318
318
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
319
|
-
'/models
|
|
320
|
-
'/model [
|
|
319
|
+
'/models 按序号列出所有可用模型',
|
|
320
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
321
|
+
'示例:先发 /models,再发 /model 2',
|
|
321
322
|
'/stop 停止当前任务',
|
|
322
323
|
'/steer 补充指令 纠偏当前任务',
|
|
323
324
|
'/status 检查连接状态',
|
|
@@ -34,8 +34,9 @@ const HELP_TEXT = [
|
|
|
34
34
|
'/workspacelist 列出工作区绝对路径',
|
|
35
35
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
36
36
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
37
|
-
'/models
|
|
38
|
-
'/model [
|
|
37
|
+
'/models 按序号列出所有可用模型',
|
|
38
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
39
|
+
'示例:先发 /models,再发 /model 2',
|
|
39
40
|
'/stop 停止当前任务',
|
|
40
41
|
'/steer 补充指令 纠偏当前任务',
|
|
41
42
|
'/status 检查连接状态',
|
|
@@ -40,8 +40,9 @@ const HELP_TEXT = [
|
|
|
40
40
|
'/workspacelist 列出工作区绝对路径',
|
|
41
41
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
42
42
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
43
|
-
'/models
|
|
44
|
-
'/model [
|
|
43
|
+
'/models 按序号列出所有可用模型',
|
|
44
|
+
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
|
45
|
+
'示例:先发 /models,再发 /model 2',
|
|
45
46
|
'/stop 停止当前任务',
|
|
46
47
|
'/steer 补充指令 纠偏当前任务',
|
|
47
48
|
'/status 检查连接状态',
|