@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "0.12.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
- '让聊天机器人轻松接入 DeepSeek Harness': 'Connect IM bots to DeepSeek Harness with ease',
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, '让聊天机器人轻松接入 DeepSeek Harness')),
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 [模型ID] 查看或切换当前会话模型',
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.12.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 [模型ID] 查看或切换当前会话模型',
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 [模型ID] 查看或切换当前会话模型',
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(`- ${safeDisplayText(id)}${id === currentId ? '(当前)' : ''}`);
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 <provider>/<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 <provider>/<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
- if (!requested.includes('/') || requested.startsWith('/') || requested.endsWith('/')) {
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 = matchingModel(catalog, requested);
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 [模型ID] 查看或切换当前会话模型',
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 [模型ID] 查看或切换当前会话模型',
37
+ '/models 按序号列出所有可用模型',
38
+ '/model [序号或完整模型ID] 查看或切换当前会话模型',
39
+ '示例:先发 /models,再发 /model 2',
39
40
  '/stop 停止当前任务',
40
41
  '/steer 补充指令 纠偏当前任务',
41
42
  '/status 检查连接状态',
@@ -184,7 +184,7 @@ function authenticatedHeaders(token) {
184
184
  function baseInfo() {
185
185
  return {
186
186
  channel_version: WEIXIN_PROTOCOL_VERSION,
187
- bot_agent: 'DeepSeekHarness/0.12.0',
187
+ bot_agent: 'DeepSeekHarness/0.13.0',
188
188
  };
189
189
  }
190
190
 
@@ -40,8 +40,9 @@ const HELP_TEXT = [
40
40
  '/workspacelist 列出工作区绝对路径',
41
41
  '/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
42
42
  '/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
43
- '/models 列出所有可用模型',
44
- '/model [模型ID] 查看或切换当前会话模型',
43
+ '/models 按序号列出所有可用模型',
44
+ '/model [序号或完整模型ID] 查看或切换当前会话模型',
45
+ '示例:先发 /models,再发 /model 2',
45
46
  '/stop 停止当前任务',
46
47
  '/steer 补充指令 纠偏当前任务',
47
48
  '/status 检查连接状态',