@xmanrui/dsh-im 4.18.0 → 4.19.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 (51) hide show
  1. package/README.en.md +8 -3
  2. package/README.md +8 -3
  3. package/lib/client.js +809 -515
  4. package/lib/index.js +269 -267
  5. package/package.json +5 -1
  6. package/plugin-src/client/bot-alias.js +92 -0
  7. package/plugin-src/client/channels/dingtalk/api.js +3 -0
  8. package/plugin-src/client/channels/dingtalk/index.js +7 -1
  9. package/plugin-src/client/channels/feishu/api.js +3 -0
  10. package/plugin-src/client/channels/feishu/index.js +7 -1
  11. package/plugin-src/client/channels/qq/api.js +3 -0
  12. package/plugin-src/client/channels/qq/index.js +9 -1
  13. package/plugin-src/client/channels/shared/token-api.js +3 -0
  14. package/plugin-src/client/channels/shared/token-channel.js +9 -2
  15. package/plugin-src/client/channels/wecom/api.js +3 -0
  16. package/plugin-src/client/channels/wecom/index.js +9 -1
  17. package/plugin-src/client/channels/wecom-app/api.js +3 -0
  18. package/plugin-src/client/channels/wecom-app/index.js +9 -1
  19. package/plugin-src/client/channels/weixin/api.js +3 -0
  20. package/plugin-src/client/channels/weixin/index.js +7 -1
  21. package/plugin-src/client/channels/whatsapp/api.js +3 -0
  22. package/plugin-src/client/channels/whatsapp/index.js +9 -1
  23. package/plugin-src/client/i18n.js +10 -0
  24. package/plugin-src/client/styles.js +26 -0
  25. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  26. package/plugin-src/host/channels/feishu/production.mjs +22 -0
  27. package/plugin-src/host/channels/feishu/rpc.mjs +14 -0
  28. package/plugin-src/host/channels/imessage/rpc.mjs +1 -0
  29. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  30. package/plugin-src/host/channels/shared/bot-alias-rpc.mjs +15 -0
  31. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  32. package/plugin-src/host/channels/slack/rpc.mjs +10 -0
  33. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  34. package/plugin-src/host/channels/wecom-app/rpc.mjs +10 -0
  35. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  36. package/plugin-src/host/channels/whatsapp/rpc.mjs +13 -0
  37. package/plugin-src/host/rpc-authority.mjs +3 -2
  38. package/plugin-src/host/session-sync-coordinator.mjs +19 -1
  39. package/scripts/verify-lan-management.mjs +176 -0
  40. package/src/channels/feishu/bridge.mjs +370 -7
  41. package/src/channels/feishu/feishu-cards.mjs +1 -1
  42. package/src/channels/feishu/feishu-runtime.mjs +6 -0
  43. package/src/channels/feishu/state-store.mjs +17 -0
  44. package/src/channels/shared/bot-alias.mjs +29 -0
  45. package/src/channels/shared/bot-workspace-store.mjs +71 -3
  46. package/src/channels/shared/i18n-en/shared-a.mjs +2 -2
  47. package/src/channels/shared/message-failure.mjs +2 -1
  48. package/src/channels/shared/model-command.mjs +3 -2
  49. package/src/channels/shared/session-sync-registry.mjs +34 -0
  50. package/src/channels/shared/text-harness-bridge.mjs +38 -0
  51. package/src/channels/telegram/telegram-runtime.mjs +77 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "4.18.0",
3
+ "version": "4.19.0",
4
4
  "description": "把十一种 IM 渠道和公网 AI Office 接入本机 DeepSeek Harness。 Connect eleven IM channels and a public AI Office to a local DeepSeek Harness.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -48,6 +48,10 @@
48
48
  {
49
49
  "name": "cherryFloris",
50
50
  "url": "https://github.com/cherryFloris"
51
+ },
52
+ {
53
+ "name": "geekyfoxlab",
54
+ "url": "https://github.com/geekyfoxlab"
51
55
  }
52
56
  ],
53
57
  "license": "MIT",
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { MAX_BOT_ALIAS_LENGTH, validateBotAlias } from '../../src/channels/shared/bot-alias.mjs';
4
+ import { h } from './i18n.js';
5
+
6
+ function AliasDialog({ bot, onSave, onClose }) {
7
+ const id = React.useId();
8
+ const dialogRef = React.useRef(null);
9
+ const inputRef = React.useRef(null);
10
+ const savingRef = React.useRef(false);
11
+ const [draft, setDraft] = React.useState(bot.alias ?? '');
12
+ const [saving, setSaving] = React.useState(false);
13
+ const [error, setError] = React.useState(null);
14
+ React.useEffect(() => {
15
+ const previous = globalThis.document?.activeElement;
16
+ dialogRef.current?.showModal?.();
17
+ inputRef.current?.focus?.();
18
+ inputRef.current?.select?.();
19
+ return () => { if (previous?.isConnected) previous.focus?.(); };
20
+ }, []);
21
+ const close = () => { if (!savingRef.current) onClose(); };
22
+ const save = async (value) => {
23
+ if (savingRef.current) return;
24
+ setError(null);
25
+ try {
26
+ const alias = validateBotAlias(value);
27
+ if (alias === (bot.alias ?? '')) { onClose(); return; }
28
+ savingRef.current = true;
29
+ setSaving(true);
30
+ await onSave(alias);
31
+ onClose();
32
+ } catch (cause) {
33
+ setError(cause?.message ?? '别名保存失败,请重试。');
34
+ } finally {
35
+ savingRef.current = false;
36
+ setSaving(false);
37
+ }
38
+ };
39
+ const content = h('dialog', {
40
+ ref: dialogRef, className: 'dim-aliasDialog', 'aria-labelledby': `${id}-title`,
41
+ 'aria-busy': saving,
42
+ onCancel: (event) => { event.preventDefault(); close(); },
43
+ onClick: (event) => event.stopPropagation(),
44
+ onKeyDown: (event) => event.stopPropagation(),
45
+ },
46
+ h('div', { className: 'dim-aliasHeader' },
47
+ h('h3', { id: `${id}-title` }, '修改别名'),
48
+ h('button', { type: 'button', className: 'dim-aliasClose', disabled: saving,
49
+ 'aria-label': '关闭修改别名', onClick: close }, '×')),
50
+ h('div', { className: 'dim-aliasOriginal' }, h('span', null, '原名称'),
51
+ h('span', null, bot.originalName ?? bot.name)),
52
+ h('label', { htmlFor: `${id}-input` }, '别名'),
53
+ h('input', { id: `${id}-input`, ref: inputRef, value: draft, disabled: saving,
54
+ maxLength: MAX_BOT_ALIAS_LENGTH, placeholder: '例如:客服助手',
55
+ 'aria-describedby': `${id}-help`,
56
+ onChange: (event) => setDraft(event.target.value),
57
+ onKeyDown: (event) => {
58
+ if (event.key === 'Enter' && !event.nativeEvent?.isComposing) {
59
+ event.preventDefault(); void save(draft);
60
+ }
61
+ },
62
+ }),
63
+ h('p', { id: `${id}-help`, className: 'dim-aliasHelp' }, '仅更改显示名称,留空则显示原名称。'),
64
+ error ? h('p', { className: 'dim-aliasError', role: 'alert' }, error) : null,
65
+ h('div', { className: 'dim-aliasFooter' },
66
+ h('button', { type: 'button', className: 'dim-aliasRestore',
67
+ disabled: saving || !bot.alias, onClick: () => void save('') }, '恢复原名称'),
68
+ h('div', { className: 'dim-aliasActions' },
69
+ h('button', { type: 'button', disabled: saving, onClick: close }, '取消'),
70
+ h('button', { type: 'button', className: 'dim-aliasSave', disabled: saving,
71
+ onClick: () => void save(draft) }, saving ? '保存中…' : '保存'))));
72
+ return globalThis.document?.body ? createPortal(content, document.body) : content;
73
+ }
74
+
75
+ export function BotName({ bot, id, disabled = false, onSave }) {
76
+ const [open, setOpen] = React.useState(false);
77
+ return h('div', { className: 'dim-aliasName' },
78
+ h('h3', { id, title: bot.name }, bot.name),
79
+ h('span', {
80
+ className: 'dim-aliasEntry',
81
+ onClick: (event) => event.stopPropagation(),
82
+ onKeyDown: (event) => event.stopPropagation(),
83
+ },
84
+ h('button', { type: 'button', className: 'dim-aliasEdit',
85
+ 'aria-label': '修改别名', title: '修改别名', 'aria-haspopup': 'dialog',
86
+ disabled: disabled || typeof onSave !== 'function', onClick: () => setOpen(true) },
87
+ h('svg', { width: 14, height: 14, viewBox: '0 0 24 24', fill: 'none',
88
+ stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round',
89
+ 'aria-hidden': true },
90
+ h('path', { d: 'm16 3 5 5M3 21l5-1L21 7a2.1 2.1 0 0 0-5-5L3 15Z' }))),
91
+ open ? h(AliasDialog, { bot, onSave, onClose: () => setOpen(false) }) : null));
92
+ }
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -19,6 +20,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
19
20
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
20
21
  setContextEnhancement: 'bot.context-enhancement.set',
21
22
  setAccessPolicy: 'bot.access-policy.set',
23
+ setAlias: 'bot.alias.set',
22
24
  });
23
25
 
24
26
  const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
@@ -190,6 +192,7 @@ function normalizeBot(value) {
190
192
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
191
193
  : {}),
192
194
  bot: {
195
+ ...normalizeBotAlias(bot),
193
196
  name: optionalString(bot.name, 100) ?? '钉钉机器人',
194
197
  clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? '已安全保存',
195
198
  },
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
@@ -244,6 +245,7 @@ export function AccountCard({
244
245
  removing,
245
246
  onReconnect,
246
247
  onWorkspaceSave,
248
+ onAliasSave,
247
249
  onModelSave,
248
250
  onAgentPresetSave,
249
251
  onContextEnhancementSave,
@@ -263,7 +265,7 @@ export function AccountCard({
263
265
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
264
266
  h('div', { className: 'ddt-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(DingtalkIcon, { size: 29 })),
265
267
  h('div', { className: 'dim-botName' },
266
- h('h3', { title: account.bot.name }, account.bot.name),
268
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }),
267
269
  h('p', { title: account.bot.clientIdMasked }, account.bot.clientIdMasked))),
268
270
  h('div', {
269
271
  className: 'dim-botCardTools',
@@ -349,6 +351,7 @@ function AccountList(props) {
349
351
  removing: props.removeTarget === account.botId,
350
352
  onReconnect: () => props.onReconnect(account),
351
353
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
354
+ onAliasSave: (alias) => props.onAliasSave(account, alias),
352
355
  onModelSave: (model) => props.onModelSave(account, model),
353
356
  onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
354
357
  onContextEnhancementSave: (config) => props.onContextEnhancementSave(account, config),
@@ -928,6 +931,9 @@ export function DingtalkSettingsTab({ rpcCall }) {
928
931
  removeTarget,
929
932
  onReconnect: (account) => void reconnect(account),
930
933
  onWorkspaceSave: saveWorkspace,
934
+ onAliasSave: (account, alias) => saveBotSetting(
935
+ account, 'alias', DINGTALK_ENDPOINTS.setAlias, { alias },
936
+ ),
931
937
  onModelSave: (account, selectedModel) => saveBotSetting(
932
938
  account, 'model', DINGTALK_ENDPOINTS.setModel, { model: selectedModel },
933
939
  ),
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  /**
2
3
  * Browser-safe contract for the Feishu Host plugin.
3
4
  *
@@ -31,6 +32,7 @@ export const FEISHU_ENDPOINTS = Object.freeze({
31
32
  setAgentPreset: "bot.preset.set",
32
33
  setContextEnhancement: "bot.context-enhancement.set",
33
34
  setAccessPolicy: "bot.access-policy.set",
35
+ setAlias: 'bot.alias.set',
34
36
  setGroupResponseMode: "bot.group-response-mode.set",
35
37
  setGroupTopicReply: "bot.group-topic-reply.set",
36
38
  setStepPush: "bot.step-push.set",
@@ -157,6 +159,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
157
159
  function normalizeBot(value) {
158
160
  const source = isRecord(value) ? value : {};
159
161
  return {
162
+ ...normalizeBotAlias(source),
160
163
  name: optionalString(source.name) ?? "飞书机器人",
161
164
  avatarUrl: optionalString(source.avatarUrl),
162
165
  appIdMasked: optionalString(source.appIdMasked),
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from "react";
2
3
 
3
4
  import { FeishuLogoGlyph } from "../../channel-logos.js";
@@ -560,6 +561,7 @@ export function BotCard({
560
561
  onReconnect,
561
562
  onRepairCallback,
562
563
  onWorkspaceSave,
564
+ onAliasSave,
563
565
  onModelSave,
564
566
  onAgentPresetSave,
565
567
  onContextEnhancementSave,
@@ -600,7 +602,7 @@ export function BotCard({
600
602
  h("div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" },
601
603
  h(FeishuLogoGlyph, { size: 34 })),
602
604
  h("div", { className: "bxf-botName dim-botName" },
603
- h("h3", { id: titleId, title: bot.name }, bot.name),
605
+ h(BotName, { bot, id: titleId, disabled: Boolean(busy), onSave: onAliasSave }),
604
606
  h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")),
605
607
  ),
606
608
  h("div", {
@@ -747,6 +749,7 @@ function BotList(props) {
747
749
  onReconnect: () => props.onReconnect(bot),
748
750
  onRepairCallback: () => props.onRepairCallback(bot),
749
751
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
752
+ onAliasSave: (alias) => props.onAliasSave(bot, alias),
750
753
  onModelSave: (model) => props.onModelSave(bot, model),
751
754
  onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
752
755
  onContextEnhancementSave: (config) => props.onContextEnhancementSave(bot, config),
@@ -1517,6 +1520,9 @@ export function FeishuSettingsTab({ rpcCall }) {
1517
1520
  onReconnect: (bot) => void reconnectOneBot(bot),
1518
1521
  onRepairCallback: repairCallback,
1519
1522
  onWorkspaceSave: saveWorkspace,
1523
+ onAliasSave: (connection, alias) => saveBotSetting(
1524
+ connection, 'alias', FEISHU_ENDPOINTS.setAlias, { alias },
1525
+ ),
1520
1526
  onModelSave: (connection, selectedModel) => saveBotSetting(
1521
1527
  connection, "model", FEISHU_ENDPOINTS.setModel, { model: selectedModel },
1522
1528
  ),
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -19,6 +20,7 @@ export const QQ_ENDPOINTS = Object.freeze({
19
20
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
20
21
  setContextEnhancement: 'bot.context-enhancement.set',
21
22
  setAccessPolicy: 'bot.access-policy.set',
23
+ setAlias: 'bot.alias.set',
22
24
  });
23
25
 
24
26
  const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
@@ -99,6 +101,7 @@ function normalizeBot(value) {
99
101
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
100
102
  : {}),
101
103
  bot: {
104
+ ...normalizeBotAlias(value.bot),
102
105
  name: text(value.bot?.name, 'QQ机器人', 100),
103
106
  appIdMasked: text(value.bot?.appIdMasked, '应用标识已安全保存', 140),
104
107
  },
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { QqLogoGlyph } from '../../channel-logos.js';
@@ -173,6 +174,7 @@ export function AccountCard({
173
174
  removing,
174
175
  onReconnect,
175
176
  onWorkspaceSave,
177
+ onAliasSave,
176
178
  onModelSave,
177
179
  onAgentPresetSave,
178
180
  onContextEnhancementSave,
@@ -191,7 +193,7 @@ export function AccountCard({
191
193
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
192
194
  h('div', { className: 'ddt-avatar dim-botAvatar dqq-avatar', 'aria-hidden': 'true' }, h(QqLogoGlyph, { size: 29 })),
193
195
  h('div', { className: 'dim-botName' },
194
- h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
196
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }), h('p', null, account.bot.appIdMasked))),
195
197
  h('div', {
196
198
  className: 'dim-botCardTools',
197
199
  // The header is the collapse toggle; keep inner controls clickable.
@@ -504,6 +506,12 @@ export function QqSettingsTab({ rpcCall }) {
504
506
  QQ_ENDPOINTS.setWorkspace,
505
507
  { botId: account.botId, workspace },
506
508
  ),
509
+ onAliasSave: (alias) => botAction(
510
+ account,
511
+ 'alias',
512
+ QQ_ENDPOINTS.setAlias,
513
+ { botId: account.botId, alias },
514
+ ),
507
515
  onModelSave: (selectedModel) => botAction(
508
516
  account,
509
517
  'model',
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -35,6 +36,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
35
36
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
36
37
  setContextEnhancement: 'bot.context-enhancement.set',
37
38
  setAccessPolicy: 'bot.access-policy.set',
39
+ setAlias: 'bot.alias.set',
38
40
  });
39
41
 
40
42
  export function createTokenChannelApi(channel, connectionSummary, {
@@ -69,6 +71,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
69
71
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
70
72
  : {}),
71
73
  bot: {
74
+ ...normalizeBotAlias(value.bot),
72
75
  name: text(value.bot?.name, `${channel}机器人`, 100),
73
76
  username: text(value.bot?.username, '', 100),
74
77
  idMasked: text(value.bot?.idMasked, '机器人标识已安全保存', 140),
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { CredentialActionIcon, CredentialBindingPanel } from '../../credential-binding.js';
@@ -81,7 +82,7 @@ export function createTokenChannelSettings(definition) {
81
82
  accountSettingsEndpoint = null,
82
83
  } = definition;
83
84
 
84
- function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
85
+ function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAliasSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
85
86
  const state = busy === 'reconnect' ? 'connecting' : account.state;
86
87
  const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
87
88
  const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
@@ -96,7 +97,7 @@ export function createTokenChannelSettings(definition) {
96
97
  h('div', { className: `ddt-avatar dim-botAvatar ${avatarClass}`, 'aria-hidden': 'true' },
97
98
  h(LogoGlyph, { size: 29 })),
98
99
  h('div', { className: 'dim-botName' },
99
- h('h3', null, account.bot.name), h('p', null, identity))),
100
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }), h('p', null, identity))),
100
101
  h('div', {
101
102
  className: 'dim-botCardTools',
102
103
  // The header is the collapse toggle; keep inner controls clickable.
@@ -345,6 +346,12 @@ export function createTokenChannelSettings(definition) {
345
346
  endpoints.setWorkspace,
346
347
  { botId: account.botId, workspace },
347
348
  ),
349
+ onAliasSave: (alias) => botAction(
350
+ account,
351
+ 'alias',
352
+ endpoints.setAlias,
353
+ { botId: account.botId, alias },
354
+ ),
348
355
  onModelSave: (selectedModel) => botAction(
349
356
  account,
350
357
  'model',
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -19,6 +20,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
19
20
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
20
21
  setContextEnhancement: 'bot.context-enhancement.set',
21
22
  setAccessPolicy: 'bot.access-policy.set',
23
+ setAlias: 'bot.alias.set',
22
24
  });
23
25
 
24
26
  const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
@@ -108,6 +110,7 @@ function normalizeBot(value) {
108
110
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
109
111
  : {}),
110
112
  bot: {
113
+ ...normalizeBotAlias(value.bot),
111
114
  name: text(value.bot?.name, '企业微信机器人', 100),
112
115
  appIdMasked: text(value.bot?.appIdMasked, '应用标识已安全保存', 140),
113
116
  },
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { WecomLogoGlyph } from '../../channel-logos.js';
@@ -172,6 +173,7 @@ export function AccountCard({
172
173
  removing,
173
174
  onReconnect,
174
175
  onWorkspaceSave,
176
+ onAliasSave,
175
177
  onModelSave,
176
178
  onAgentPresetSave,
177
179
  onContextEnhancementSave,
@@ -190,7 +192,7 @@ export function AccountCard({
190
192
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
191
193
  h('div', { className: 'ddt-avatar dim-botAvatar dwecom-avatar', 'aria-hidden': 'true' }, h(WecomLogoGlyph, { size: 29 })),
192
194
  h('div', { className: 'dim-botName' },
193
- h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
195
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }), h('p', null, account.bot.appIdMasked))),
194
196
  h('div', {
195
197
  className: 'dim-botCardTools',
196
198
  // The header is the collapse toggle; keep inner controls clickable.
@@ -535,6 +537,12 @@ export function WecomSettingsTab({ rpcCall }) {
535
537
  WECOM_ENDPOINTS.setWorkspace,
536
538
  { botId: account.botId, workspace },
537
539
  ),
540
+ onAliasSave: (alias) => botAction(
541
+ account,
542
+ 'alias',
543
+ WECOM_ENDPOINTS.setAlias,
544
+ { botId: account.botId, alias },
545
+ ),
538
546
  onModelSave: (selectedModel) => botAction(
539
547
  account,
540
548
  'model',
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -18,6 +19,7 @@ export const WECOM_APP_ENDPOINTS = Object.freeze({
18
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
19
20
  setContextEnhancement: 'bot.context-enhancement.set',
20
21
  setAccessPolicy: 'bot.access-policy.set',
22
+ setAlias: 'bot.alias.set',
21
23
  });
22
24
 
23
25
  const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
@@ -82,6 +84,7 @@ function normalizeBot(value) {
82
84
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
83
85
  : {}),
84
86
  bot: {
87
+ ...normalizeBotAlias(value.bot),
85
88
  name: text(value.bot?.name, '企业微信应用', 100),
86
89
  corpIdMasked: text(value.bot?.corpIdMasked, '企业 ID 已保存', 140),
87
90
  agentId: text(value.bot?.agentId, '', 32),
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { WecomLogoGlyph } from '../../channel-logos.js';
@@ -246,6 +247,7 @@ export function AccountCard({
246
247
  onSettingsSave,
247
248
  onSecretReset,
248
249
  onWorkspaceSave,
250
+ onAliasSave,
249
251
  onModelSave,
250
252
  onAgentPresetSave,
251
253
  onContextEnhancementSave,
@@ -265,7 +267,7 @@ export function AccountCard({
265
267
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
266
268
  h('div', { className: 'ddt-avatar dim-botAvatar dwecomapp-avatar', 'aria-hidden': 'true' }, h(WecomLogoGlyph, { size: 29 })),
267
269
  h('div', { className: 'dim-botName' },
268
- h('h3', null, account.bot.name),
270
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }),
269
271
  h('p', null, `${account.bot.corpIdMasked} · AgentId ${account.bot.agentId}`))),
270
272
  h('div', {
271
273
  className: 'dim-botCardTools',
@@ -552,6 +554,12 @@ export function WecomAppSettingsTab({ rpcCall }) {
552
554
  WECOM_APP_ENDPOINTS.setWorkspace,
553
555
  { botId: account.botId, workspace },
554
556
  ),
557
+ onAliasSave: (alias) => botAction(
558
+ account,
559
+ 'alias',
560
+ WECOM_APP_ENDPOINTS.setAlias,
561
+ { botId: account.botId, alias },
562
+ ),
555
563
  onModelSave: (selectedModel) => botAction(
556
564
  account,
557
565
  'model',
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -18,6 +19,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
18
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
19
20
  setContextEnhancement: 'bot.context-enhancement.set',
20
21
  setAccessPolicy: 'bot.access-policy.set',
22
+ setAlias: 'bot.alias.set',
21
23
  });
22
24
 
23
25
  const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
@@ -133,6 +135,7 @@ function normalizeBot(value) {
133
135
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
134
136
  : {}),
135
137
  bot: {
138
+ ...normalizeBotAlias(value.bot),
136
139
  name: string(value.bot.name, '微信机器人'),
137
140
  accountIdMasked: string(value.bot.accountIdMasked, '已安全保存'),
138
141
  },
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { WeixinLogoGlyph } from '../../channel-logos.js';
@@ -214,6 +215,7 @@ export function AccountCard({
214
215
  removing,
215
216
  onReconnect,
216
217
  onWorkspaceSave,
218
+ onAliasSave,
217
219
  onModelSave,
218
220
  onAgentPresetSave,
219
221
  onContextEnhancementSave,
@@ -231,7 +233,7 @@ export function AccountCard({
231
233
  header: h('div', { className: 'dxw-accountTop dim-botCardTop' },
232
234
  h('div', { className: 'dxw-accountIdentity dim-botIdentity' },
233
235
  h('div', { className: 'dxw-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })),
234
- h('div', { className: 'dim-botName' }, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))),
236
+ h('div', { className: 'dim-botName' }, h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }), h('p', null, account.bot.accountIdMasked))),
235
237
  h('div', {
236
238
  className: 'dim-botCardTools',
237
239
  // The header is the collapse toggle; keep inner controls clickable.
@@ -318,6 +320,7 @@ function AccountList(props) {
318
320
  removing: props.removeTarget === account.botId,
319
321
  onReconnect: () => props.onReconnect(account),
320
322
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
323
+ onAliasSave: (alias) => props.onAliasSave(account, alias),
321
324
  onModelSave: (model) => props.onModelSave(account, model),
322
325
  onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
323
326
  onContextEnhancementSave: (config) => props.onContextEnhancementSave(account, config),
@@ -752,6 +755,9 @@ export function WeixinSettingsTab({ rpcCall }) {
752
755
  removeTarget,
753
756
  onReconnect: (account) => void reconnect(account),
754
757
  onWorkspaceSave: saveWorkspace,
758
+ onAliasSave: (account, alias) => saveBotSetting(
759
+ account, 'alias', WEIXIN_ENDPOINTS.setAlias, { alias },
760
+ ),
755
761
  onModelSave: (account, selectedModel) => saveBotSetting(
756
762
  account, 'model', WEIXIN_ENDPOINTS.setModel, { model: selectedModel },
757
763
  ),
@@ -1,3 +1,4 @@
1
+ import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
1
2
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
3
  import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
3
4
  import { normalizeLastMessageError } from '../../last-message-error.js';
@@ -14,6 +15,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
14
15
  reconnectBot: 'bot.reconnect',
15
16
  deleteBot: 'bot.delete',
16
17
  setAccessPolicy: 'bot.access-policy.set',
18
+ setAlias: 'bot.alias.set',
17
19
  setWorkspace: 'bot.workspace.set',
18
20
  setModel: SET_MODEL_ENDPOINT,
19
21
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
@@ -99,6 +101,7 @@ function normalizeBot(value) {
99
101
  ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
100
102
  : {}),
101
103
  bot: {
104
+ ...normalizeBotAlias(value.bot),
102
105
  name: text(value.bot?.name, 'WhatsApp机器人', 100),
103
106
  idMasked: text(value.bot?.idMasked, 'WhatsApp账号', 140),
104
107
  },
@@ -1,3 +1,4 @@
1
+ import { BotName } from '../../bot-alias.js';
1
2
  import * as React from 'react';
2
3
 
3
4
  import { WhatsappLogoGlyph } from '../../channel-logos.js';
@@ -191,6 +192,7 @@ export function WhatsappAccountCard({
191
192
  removing,
192
193
  onReconnect,
193
194
  onWorkspaceSave,
195
+ onAliasSave,
194
196
  onModelSave,
195
197
  onAgentPresetSave,
196
198
  onContextEnhancementSave,
@@ -213,7 +215,7 @@ export function WhatsappAccountCard({
213
215
  'aria-hidden': 'true',
214
216
  }, h(WhatsappLogoGlyph, { size: 29 })),
215
217
  h('div', { className: 'dim-botName' },
216
- h('h3', null, account.bot.name), h('p', null, account.bot.idMasked))),
218
+ h(BotName, { bot: account.bot, disabled: Boolean(busy), onSave: onAliasSave }), h('p', null, account.bot.idMasked))),
217
219
  h('div', {
218
220
  className: 'dim-botCardTools',
219
221
  // The header is the collapse toggle; keep inner controls clickable.
@@ -512,6 +514,12 @@ export function WhatsappSettingsTab({ rpcCall }) {
512
514
  WHATSAPP_ENDPOINTS.setWorkspace,
513
515
  { botId: account.botId, workspace },
514
516
  ),
517
+ onAliasSave: (alias) => botAction(
518
+ account,
519
+ 'alias',
520
+ WHATSAPP_ENDPOINTS.setAlias,
521
+ { botId: account.botId, alias },
522
+ ),
515
523
  onModelSave: (selectedModel) => botAction(
516
524
  account,
517
525
  'model',
@@ -4,6 +4,16 @@ export const IM_LOCALE_NAMESPACE = 'dsh-im';
4
4
 
5
5
  const EN = Object.freeze({
6
6
  '$locale': 'en',
7
+ '修改别名': 'Edit alias',
8
+ '关闭修改别名': 'Close alias editor',
9
+ '原名称': 'Original name',
10
+ '别名': 'Alias',
11
+ '恢复原名称': 'Restore original name',
12
+ '例如:客服助手': 'e.g. Customer support',
13
+ '仅更改显示名称,留空则显示原名称。': 'Only changes the display name. Leave blank to use the original name.',
14
+ '别名保存失败,请重试。': 'Could not save the alias. Try again.',
15
+ '别名不能包含换行或控制字符,且最多 80 个字符。': 'Use at most 80 characters without line breaks or control characters.',
16
+ '请输入有效的别名(最多 80 个字符)。': 'Enter a valid alias (up to 80 characters).',
7
17
  ' macOS Messages 连接': ' macOS Messages connection',
8
18
  '接入 iMessage': 'Connect iMessage',
9
19
  '先在 macOS 系统设置中授予 Messages 权限。': 'Grant Messages permissions in macOS System Settings first.',
@@ -1,6 +1,32 @@
1
1
  export const IM_STYLE_ID = 'xmanrui-dsh-im-settings';
2
2
 
3
3
  const CSS = String.raw`
4
+ .dim-aliasName { display: flex; align-items: center; gap: 4px; min-width: 0; }
5
+ .dim-aliasName h3 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6
+ .dim-aliasEntry { display: inline-flex; flex: none; }
7
+ .dim-aliasEdit { display: grid; place-items: center; width: 28px; height: 28px; padding: 4px; border: 0; border-radius: 5px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; cursor: pointer; }
8
+ .dim-aliasEdit:hover:not(:disabled) { color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
9
+ .dim-aliasDialog { box-sizing: border-box; width: min(380px, calc(100% - 32px)); max-height: calc(100dvh - 32px); overflow-y: auto; padding: 22px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 12px 36px rgb(0 0 0 / 18%); font: 13px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
10
+ .dim-aliasDialog * { box-sizing: border-box; }
11
+ .dim-aliasDialog::backdrop { background: rgb(15 17 21 / 30%); }
12
+ .dim-aliasHeader { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 18px; }
13
+ .dim-aliasHeader h3 { margin: 0; font-size: 16px; }
14
+ .dim-aliasDialog button { font: inherit; cursor: pointer; }
15
+ .dim-aliasDialog .dim-aliasClose { width: 28px; height: 28px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #646a73); font-size: 20px; }
16
+ .dim-aliasOriginal { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 10px 12px; margin-bottom: 18px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f5f6f7); overflow-wrap: anywhere; }
17
+ .dim-aliasOriginal > span:first-child { flex: none; color: var(--dsw-alias-label-secondary, #646a73); }
18
+ .dim-aliasDialog label { display: block; margin-bottom: 7px; }
19
+ .dim-aliasDialog input { width: 100%; min-height: 38px; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 6px; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; }
20
+ .dim-aliasHelp { margin: 8px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
21
+ .dim-aliasError { color: var(--dsw-alias-state-danger-primary, #c53030); overflow-wrap: anywhere; }
22
+ .dim-aliasFooter { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px; margin-top: 24px; }
23
+ .dim-aliasRestore { padding: 4px 0; border: 0; color: var(--dsw-alias-state-business-primary, #3370ff); background: transparent; }
24
+ .dim-aliasActions { display: flex; gap: 8px; margin-left: auto; }
25
+ .dim-aliasActions button { padding: 7px 14px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 6px; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); }
26
+ .dim-aliasActions .dim-aliasSave { color: #fff; border-color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-state-business-primary, #3370ff); }
27
+ .dim-aliasEdit:disabled, .dim-aliasDialog button:disabled, .dim-aliasDialog input:disabled { opacity: .55; cursor: not-allowed; }
28
+ .dim-aliasEdit:focus-visible, .dim-aliasDialog button:focus-visible, .dim-aliasDialog input:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
29
+ @media (pointer: coarse) { .dim-aliasEdit, .dim-aliasDialog button { min-width: 44px; min-height: 44px; } .dim-aliasDialog input { font-size: 16px; } }
4
30
  .dim-page {
5
31
  --dim-blue: var(--dsw-alias-state-business-primary, #3370ff);
6
32
  --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent);