@xmanrui/dsh-im 4.14.0 → 4.16.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.
Files changed (69) hide show
  1. package/README.en.md +10 -2
  2. package/README.md +17 -6
  3. package/lib/client.js +1980 -616
  4. package/lib/index.js +303 -293
  5. package/package.json +6 -2
  6. package/plugin-src/client/access-policy-settings.js +6 -0
  7. package/plugin-src/client/channels/feishu/index.js +16 -4
  8. package/plugin-src/client/channels/wecom-app/api.js +124 -0
  9. package/plugin-src/client/channels/wecom-app/index.js +612 -0
  10. package/plugin-src/client/channels/wecom-app/styles.js +25 -0
  11. package/plugin-src/client/delivery-settings.js +7 -0
  12. package/plugin-src/client/i18n.js +64 -1
  13. package/plugin-src/client/index.js +21 -0
  14. package/plugin-src/client/model-setting.js +135 -69
  15. package/plugin-src/client/session-channel-logos.js +241 -0
  16. package/plugin-src/client/styles.js +22 -7
  17. package/plugin-src/host/channels/dingtalk/index.mjs +8 -15
  18. package/plugin-src/host/channels/discord/index.mjs +8 -10
  19. package/plugin-src/host/channels/feishu/index.mjs +8 -15
  20. package/plugin-src/host/channels/office/index.mjs +9 -5
  21. package/plugin-src/host/channels/qq/index.mjs +8 -15
  22. package/plugin-src/host/channels/shared/access-policy-production.mjs +1 -1
  23. package/plugin-src/host/channels/shared/model-setting-rpc.mjs +1 -2
  24. package/plugin-src/host/channels/shared/startup-error.mjs +53 -0
  25. package/plugin-src/host/channels/shared/startup.mjs +47 -0
  26. package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
  27. package/plugin-src/host/channels/slack/index.mjs +8 -10
  28. package/plugin-src/host/channels/telegram/index.mjs +8 -10
  29. package/plugin-src/host/channels/wecom/index.mjs +8 -15
  30. package/plugin-src/host/channels/wecom/rpc.mjs +6 -1
  31. package/plugin-src/host/channels/wecom-app/index.mjs +33 -0
  32. package/plugin-src/host/channels/wecom-app/production.mjs +217 -0
  33. package/plugin-src/host/channels/wecom-app/rpc.mjs +225 -0
  34. package/plugin-src/host/channels/weixin/index.mjs +8 -15
  35. package/plugin-src/host/channels/whatsapp/index.mjs +8 -15
  36. package/plugin-src/host/delivery-adapter.mjs +4 -0
  37. package/plugin-src/host/delivery-suggestions.mjs +4 -0
  38. package/plugin-src/host/index.mjs +21 -2
  39. package/plugin-src/host/session-title-prefix.mjs +122 -0
  40. package/scripts/verify-model-setting.mjs +54 -0
  41. package/scripts/verify-session-channel-logos.mjs +59 -0
  42. package/scripts/verify-session-title-prefix.mjs +149 -0
  43. package/src/channels/feishu/bridge.mjs +305 -12
  44. package/src/channels/feishu/feishu-cards.mjs +38 -0
  45. package/src/channels/feishu/feishu-channel.mjs +88 -20
  46. package/src/channels/feishu/plugin-controller.mjs +1 -0
  47. package/src/channels/feishu/repair-manager.mjs +3 -2
  48. package/src/channels/shared/bot-workspace-store.mjs +11 -3
  49. package/src/channels/shared/command-catalog.mjs +102 -0
  50. package/src/channels/shared/i18n-en/feishu.mjs +14 -2
  51. package/src/channels/shared/i18n-en/shared-a.mjs +3 -0
  52. package/src/channels/shared/i18n-en/shared-c.mjs +9 -0
  53. package/src/channels/shared/i18n-en/wecom-app.mjs +33 -0
  54. package/src/channels/shared/i18n-en.mjs +2 -0
  55. package/src/channels/shared/model-setting.mjs +43 -8
  56. package/src/channels/shared/session-channel-labels.mjs +26 -0
  57. package/src/channels/shared/text-harness-bridge.mjs +2 -26
  58. package/src/channels/telegram/telegram-api.mjs +18 -6
  59. package/src/channels/telegram/telegram-runtime.mjs +34 -32
  60. package/src/channels/wecom/send-error.mjs +31 -0
  61. package/src/channels/wecom/wecom-bridge.mjs +59 -17
  62. package/src/channels/wecom-app/callback-server.mjs +471 -0
  63. package/src/channels/wecom-app/config-store.mjs +240 -0
  64. package/src/channels/wecom-app/harness-client.mjs +11 -0
  65. package/src/channels/wecom-app/state-store.mjs +107 -0
  66. package/src/channels/wecom-app/wecom-app-api.mjs +432 -0
  67. package/src/channels/wecom-app/wecom-app-bridge.mjs +1059 -0
  68. package/src/channels/wecom-app/wecom-app-controller.mjs +440 -0
  69. package/src/channels/wecom-app/wecom-app-runtime.mjs +221 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "4.14.0",
4
- "description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
3
+ "version": "4.16.0",
4
+ "description": "把十种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect ten IM channels and a public AI Office to a local DeepSeek Harness.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
@@ -35,6 +35,10 @@
35
35
  {
36
36
  "name": "Chan-0312",
37
37
  "url": "https://github.com/Chan-0312"
38
+ },
39
+ {
40
+ "name": "BuvkB",
41
+ "url": "https://github.com/BuvkB"
38
42
  }
39
43
  ],
40
44
  "license": "MIT",
@@ -33,6 +33,12 @@ export const ACCESS_CHANNEL_DEFINITIONS = Object.freeze({
33
33
  groupUserLabel: '群成员用户 ID',
34
34
  groupPlaceholder: '填写 userid',
35
35
  }),
36
+ wecomApp: Object.freeze({
37
+ directUserLabel: '企业微信用户 ID',
38
+ directPlaceholder: '填写 userid',
39
+ groupUserLabel: '群成员用户 ID',
40
+ groupPlaceholder: '填写 userid',
41
+ }),
36
42
  qq: Object.freeze({
37
43
  directUserLabel: 'QQ User Open ID',
38
44
  directPlaceholder: '填写 user_openid',
@@ -310,7 +310,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
310
310
  ? "使用飞书确认群消息权限"
311
311
  : "使用飞书扫码创建机器人"),
312
312
  h("p", null, repairing
313
- ? "扫码会更新现有飞书应用,增量补充当前缺少的卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”)、上传机器人图片或文件所需的 im:resource,以及原生命令面板所需的 application:app_slash_command:read / write;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。"
313
+ ? "扫码会更新现有飞书应用,增量补充当前缺少的卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”)、上传机器人图片或文件所需的 im:resource、接收群内其他机器人 @ 当前机器人所需的 im:message.group_at_msg.include_bot:readonly,以及原生命令面板所需的 application:app_slash_command:read / write;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。"
314
314
  : grantingGroupMessages
315
315
  ? "扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。"
316
316
  : "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"),
@@ -467,6 +467,7 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
467
467
  /** Toggle row for 分步直推, mirroring the group-topic reply toggle pattern. */
468
468
  function StepPushEditor({ value = false, disabled = false, onSave }) {
469
469
  const titleId = React.useId();
470
+ const helpId = `${titleId}-help`;
470
471
  const current = value === true ? "on" : "off";
471
472
  const [saving, setSaving] = React.useState(false);
472
473
  const [error, setError] = React.useState(null);
@@ -490,7 +491,20 @@ function StepPushEditor({ value = false, disabled = false, onSave }) {
490
491
  "aria-labelledby": titleId,
491
492
  },
492
493
  h("div", { className: "dim-feishuGroupControlHeader" },
493
- h("h3", { id: titleId }, "分步直推"),
494
+ h("div", { className: "dim-presetTitle" },
495
+ h("h3", { id: titleId }, "分步直推"),
496
+ h("span", { className: "dim-presetHelp" },
497
+ h("button", {
498
+ type: "button",
499
+ className: "dim-presetHelpButton",
500
+ "aria-label": "查看分步直推说明",
501
+ "aria-describedby": helpId,
502
+ }, h("span", { "aria-hidden": "true" }, "?")),
503
+ h("span", {
504
+ id: helpId,
505
+ className: "dim-presetTooltip",
506
+ role: "tooltip",
507
+ }, "开启后逐步推送工具调用与过程说明"))),
494
508
  saving
495
509
  ? h("span", { className: "dim-feishuGroupControlStatus", role: "status" }, "保存中…")
496
510
  : null),
@@ -503,8 +517,6 @@ function StepPushEditor({ value = false, disabled = false, onSave }) {
503
517
  },
504
518
  h("option", { value: "off" }, "关闭(保持流式卡模式)"),
505
519
  h("option", { value: "on" }, "开启(逐步推送工具调用与过程说明)")),
506
- h("p", { className: "dim-feishuGroupHelp" },
507
- "开启后逐步推送工具调用与过程说明"),
508
520
  error ? h("p", {
509
521
  className: "dim-feishuGroupError",
510
522
  role: "alert",
@@ -0,0 +1,124 @@
1
+ import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
+ import { normalizeLastMessageError } from '../../last-message-error.js';
4
+ import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
5
+ import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
6
+
7
+ export const WECOM_APP_RPC_CHANNEL = '/wecom-app';
8
+
9
+ export const WECOM_APP_ENDPOINTS = Object.freeze({
10
+ status: 'connection.status',
11
+ bindApp: 'bot.bind',
12
+ updateSettings: 'bot.settings.update',
13
+ resetCallbackSecret: 'bot.callback-secret.reset',
14
+ reconnectBot: 'bot.reconnect',
15
+ deleteBot: 'bot.delete',
16
+ setWorkspace: 'bot.workspace.set',
17
+ setModel: SET_MODEL_ENDPOINT,
18
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
19
+ setContextEnhancement: 'bot.context-enhancement.set',
20
+ setAccessPolicy: 'bot.access-policy.set',
21
+ });
22
+
23
+ const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
24
+
25
+ function isRecord(value) {
26
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
27
+ }
28
+
29
+ function text(value, fallback, max = 240) {
30
+ return typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : fallback;
31
+ }
32
+
33
+ function id(value) {
34
+ const result = text(value, '', 128);
35
+ return /^[a-z\d_-]+$/i.test(result) ? result : undefined;
36
+ }
37
+
38
+ function optionalUrl(value) {
39
+ const raw = text(value, '', 512);
40
+ return /^https?:\/\//iu.test(raw) ? raw : null;
41
+ }
42
+
43
+ function timestamp(value) {
44
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
45
+ const parsed = typeof value === 'string' ? Date.parse(value) : Number.NaN;
46
+ return Number.isNaN(parsed) ? undefined : parsed;
47
+ }
48
+
49
+ function normalizeTestMessage(value) {
50
+ if (!isRecord(value)) return null;
51
+ if (value.sent === true) return { sent: true };
52
+ if (value.sent !== false) return null;
53
+ const code = value.code === 'test-target-unavailable'
54
+ ? 'test-target-unavailable'
55
+ : 'test-message-failed';
56
+ return { sent: false, code };
57
+ }
58
+
59
+ export function unwrapRpcResult(result) {
60
+ if (!isRecord(result) || typeof result.ok !== 'boolean') throw new Error('企业微信应用服务返回了无法识别的响应');
61
+ if (!result.ok) {
62
+ const error = new Error(text(result.error?.message, '企业微信应用操作失败'));
63
+ error.code = text(result.error?.code, 'WECOM_APP_RPC_ERROR', 80);
64
+ throw error;
65
+ }
66
+ return result.value;
67
+ }
68
+
69
+ function normalizeBot(value) {
70
+ if (!isRecord(value) || !id(value.botId)) return undefined;
71
+ const connected = value.connected === true;
72
+ const state = ACCOUNT_STATES.has(value.state) ? value.state : 'offline';
73
+ return {
74
+ botId: id(value.botId),
75
+ connected,
76
+ state: connected ? 'connected' : state,
77
+ workspace: text(value.workspace, '', 4_096),
78
+ model: normalizeModelSelection(value.model),
79
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
80
+ contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
81
+ ...(Object.hasOwn(value, 'accessPolicy')
82
+ ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
83
+ : {}),
84
+ bot: {
85
+ name: text(value.bot?.name, '企业微信应用', 100),
86
+ corpIdMasked: text(value.bot?.corpIdMasked, '企业 ID 已保存', 140),
87
+ agentId: text(value.bot?.agentId, '', 32),
88
+ apiBaseUrl: optionalUrl(value.bot?.apiBaseUrl) ?? '',
89
+ callbackBaseUrl: optionalUrl(value.bot?.callbackBaseUrl) ?? '',
90
+ streamEnabled: value.bot?.streamEnabled !== false,
91
+ callbackUrl: optionalUrl(value.bot?.callbackUrl) ?? '',
92
+ },
93
+ health: {
94
+ summary: text(value.health?.summary, connected ? '企业微信应用回调通道就绪' : '企业微信应用连接尚未就绪'),
95
+ lastCheckedAt: timestamp(value.health?.lastCheckedAt),
96
+ },
97
+ lastMessageError: normalizeLastMessageError(value.lastMessageError),
98
+ error: isRecord(value.error) ? {
99
+ code: text(value.error.code, 'WECOM_APP_ACCOUNT_ERROR', 80),
100
+ message: text(value.error.message, '企业微信应用连接尚未就绪'),
101
+ } : null,
102
+ };
103
+ }
104
+
105
+ export function normalizeSnapshot(value) {
106
+ const source = isRecord(value?.snapshot) ? value.snapshot : value;
107
+ if (!isRecord(source) || !Array.isArray(source.bots)) throw new Error('企业微信应用服务没有返回有效的机器人列表');
108
+ const bots = source.bots.map(normalizeBot).filter(Boolean);
109
+ return {
110
+ revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
111
+ bots,
112
+ totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
113
+ testMessage: normalizeTestMessage(source.testMessage),
114
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
115
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
116
+ };
117
+ }
118
+
119
+ export function presentError(error) {
120
+ return {
121
+ code: text(error?.code, 'WECOM_APP_ERROR', 80),
122
+ message: text(error?.message, '企业微信应用操作失败,请稍后重试'),
123
+ };
124
+ }