@xmanrui/dsh-im 1.0.2 → 1.2.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 (52) hide show
  1. package/README.en.md +23 -3
  2. package/README.md +23 -3
  3. package/assets/logo-dsh-im-chinese-readme-3x2.png +0 -0
  4. package/assets/logo_cn.png +0 -0
  5. package/lib/client.js +815 -560
  6. package/lib/index.js +163 -163
  7. package/package.json +1 -1
  8. package/plugin-src/client/agent-preset.js +15 -6
  9. package/plugin-src/client/channel-card-meta.js +48 -0
  10. package/plugin-src/client/channels/dingtalk/index.js +25 -19
  11. package/plugin-src/client/channels/dingtalk/styles.js +0 -6
  12. package/plugin-src/client/channels/feishu/index.js +41 -35
  13. package/plugin-src/client/channels/feishu/styles.js +0 -5
  14. package/plugin-src/client/channels/qq/index.js +24 -16
  15. package/plugin-src/client/channels/shared/token-channel.js +32 -24
  16. package/plugin-src/client/channels/wecom/index.js +24 -16
  17. package/plugin-src/client/channels/weixin/index.js +29 -23
  18. package/plugin-src/client/channels/weixin/styles.js +0 -5
  19. package/plugin-src/client/channels/whatsapp/api.js +11 -0
  20. package/plugin-src/client/channels/whatsapp/index.js +152 -23
  21. package/plugin-src/client/channels/whatsapp/styles.js +25 -0
  22. package/plugin-src/client/i18n.js +20 -0
  23. package/plugin-src/client/styles.js +23 -8
  24. package/plugin-src/host/channels/whatsapp/rpc.mjs +19 -1
  25. package/plugin-src/host/index.mjs +14 -1
  26. package/src/channels/dingtalk/dingtalk-api.mjs +215 -2
  27. package/src/channels/dingtalk/dingtalk-bridge.mjs +155 -4
  28. package/src/channels/discord/discord-api.mjs +134 -6
  29. package/src/channels/discord/discord-runtime.mjs +15 -4
  30. package/src/channels/feishu/bridge.mjs +223 -15
  31. package/src/channels/feishu/feishu-channel.mjs +227 -1
  32. package/src/channels/feishu/plugin-controller.mjs +1 -0
  33. package/src/channels/qq/qq-bridge.mjs +217 -10
  34. package/src/channels/shared/editable-message-stream.mjs +18 -1
  35. package/src/channels/shared/harness-client.mjs +99 -7
  36. package/src/channels/shared/semantic/artifact.mjs +748 -0
  37. package/src/channels/shared/semantic/delivery.mjs +153 -0
  38. package/src/channels/shared/text-harness-bridge.mjs +149 -3
  39. package/src/channels/shared/workspace-session.mjs +15 -1
  40. package/src/channels/slack/manifest.mjs +1 -0
  41. package/src/channels/slack/slack-api.mjs +167 -4
  42. package/src/channels/slack/slack-runtime.mjs +21 -5
  43. package/src/channels/telegram/telegram-api.mjs +111 -5
  44. package/src/channels/telegram/telegram-runtime.mjs +18 -4
  45. package/src/channels/wecom/wecom-bridge.mjs +260 -12
  46. package/src/channels/weixin/weixin-api.mjs +268 -2
  47. package/src/channels/weixin/weixin-bridge.mjs +134 -3
  48. package/src/channels/weixin/weixin-controller.mjs +5 -1
  49. package/src/channels/weixin/weixin-runtime.mjs +5 -1
  50. package/src/channels/whatsapp/config-store.mjs +43 -0
  51. package/src/channels/whatsapp/whatsapp-controller.mjs +22 -1
  52. package/src/channels/whatsapp/whatsapp-runtime.mjs +149 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "1.0.2",
3
+ "version": "1.2.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",
@@ -46,6 +46,7 @@ export function normalizeAgentPresetCatalog(value) {
46
46
 
47
47
  export function AgentPresetEditor({ agentPreset = '', disabled = false, onSave }) {
48
48
  const catalog = React.useContext(AgentPresetCatalogContext) ?? EMPTY_AGENT_PRESET_CATALOG;
49
+ const helpId = React.useId();
49
50
  const current = normalizeAgentPresetId(agentPreset);
50
51
  const [saving, setSaving] = React.useState(false);
51
52
  const [error, setError] = React.useState(null);
@@ -78,7 +79,20 @@ export function AgentPresetEditor({ agentPreset = '', disabled = false, onSave }
78
79
 
79
80
  return h('div', { className: 'dim-preset' },
80
81
  h('div', { className: 'dim-presetHeader' },
81
- h('span', null, 'Agent Preset'),
82
+ h('span', { className: 'dim-presetTitle' },
83
+ h('span', null, 'Agent Preset'),
84
+ h('span', { className: 'dim-presetHelp' },
85
+ h('button', {
86
+ type: 'button',
87
+ className: 'dim-presetHelpButton',
88
+ 'aria-label': '查看 Agent Preset 说明',
89
+ 'aria-describedby': helpId,
90
+ }, h('span', { 'aria-hidden': 'true' }, '?')),
91
+ h('span', {
92
+ id: helpId,
93
+ className: 'dim-presetTooltip',
94
+ role: 'tooltip',
95
+ }, '只影响新建会话;若当前聊天已有会话,先发送 /new,再发送普通消息生效。'))),
82
96
  saving ? h('span', { className: 'dim-presetStatus' }, '保存中…') : null),
83
97
  React.createElement('select', {
84
98
  className: 'dim-presetSelect',
@@ -96,11 +110,6 @@ export function AgentPresetEditor({ agentPreset = '', disabled = false, onSave }
96
110
  : item.label && item.label !== item.id ? `${item.label}(${item.id})` : item.id,
97
111
  )),
98
112
  ),
99
- h(
100
- 'small',
101
- { className: 'dim-presetHelp' },
102
- '只影响新建会话;若当前聊天已有会话,先发送 /new,再发送普通消息生效。',
103
- ),
104
113
  error || currentUnavailable ? h(
105
114
  'p',
106
115
  { className: 'dim-presetError', role: error ? 'alert' : 'status' },
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+
3
+ import { h } from './i18n.js';
4
+
5
+ export function ChannelListHeading({ className = '', id, title, connectionLabel }) {
6
+ const helpId = React.useId();
7
+ return h('div', { className: `${className} dim-listHeading`.trim() },
8
+ h('div', { className: 'dim-listTitle' },
9
+ h('h3', id ? { id } : null, title),
10
+ h('span', { className: 'dim-channelHelp' },
11
+ h('button', {
12
+ type: 'button',
13
+ className: 'dim-channelHelpButton',
14
+ 'aria-label': '查看消息通道说明',
15
+ 'aria-describedby': helpId,
16
+ }, h('span', { 'aria-hidden': 'true' }, '?')),
17
+ h('span', {
18
+ id: helpId,
19
+ className: 'dim-channelTooltip',
20
+ role: 'tooltip',
21
+ },
22
+ h('span', null, '消息通道'),
23
+ h('strong', null, connectionLabel)))));
24
+ }
25
+
26
+ export function BotStatusMeta({
27
+ className = '',
28
+ dotClassName = '',
29
+ tone,
30
+ stateLabel,
31
+ lastCheckedAt,
32
+ formatCheckedTime,
33
+ healthState,
34
+ }) {
35
+ return h('div', { className: 'dim-botHealthGroup' },
36
+ h('div', {
37
+ className: `${className} dim-botHealth`.trim(),
38
+ ...(healthState ? { 'data-health': healthState } : {}),
39
+ },
40
+ h('span', {
41
+ className: `${dotClassName} dim-healthDot`.trim(),
42
+ 'data-tone': tone,
43
+ }),
44
+ h('span', null, stateLabel)),
45
+ h('div', { className: 'dim-lastChecked' },
46
+ h('span', null, '最近检查'),
47
+ h('span', null, formatCheckedTime(lastCheckedAt))));
48
+ }
@@ -9,6 +9,7 @@ import {
9
9
  EMPTY_AGENT_PRESET_CATALOG,
10
10
  } from '../../agent-preset.js';
11
11
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
12
+ import { BotStatusMeta, ChannelListHeading } from '../../channel-card-meta.js';
12
13
  import {
13
14
  DINGTALK_ENDPOINTS,
14
15
  DINGTALK_RPC_CHANNEL,
@@ -235,13 +236,14 @@ export function AccountCard({
235
236
  h('div', { className: 'dim-botName' },
236
237
  h('h3', { title: account.bot.name }, account.bot.name),
237
238
  h('p', { title: account.bot.clientIdMasked }, account.bot.clientIdMasked))),
238
- h('div', { className: 'ddt-health dim-botHealth' },
239
- h('span', { className: 'ddt-dot dim-healthDot', 'data-tone': tone }), h('span', null, stateLabel))),
240
- h('dl', { className: 'ddt-metrics dim-botMetrics' },
241
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '消息通道'),
242
- h('dd', null, account.connected ? 'Stream 长连接' : '离线')),
243
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '最近检查'),
244
- h('dd', null, checkedTime(account.health.lastCheckedAt)))),
239
+ h(BotStatusMeta, {
240
+ className: 'ddt-health',
241
+ dotClassName: 'ddt-dot',
242
+ tone,
243
+ stateLabel,
244
+ lastCheckedAt: account.health.lastCheckedAt,
245
+ formatCheckedTime: checkedTime,
246
+ })),
245
247
  h(WorkspaceEditor, {
246
248
  workspace: account.workspace,
247
249
  disabled: Boolean(busy),
@@ -253,16 +255,17 @@ export function AccountCard({
253
255
  onSave: onAgentPresetSave,
254
256
  }),
255
257
  h('div', { className: 'ddt-accountFooter dim-cardFooter' },
256
- summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
257
- feedback ? h('div', {
258
- className: 'ddt-summary dim-cardSummary',
259
- role: 'status',
260
- }, feedback) : null,
261
- h('div', { className: 'ddt-actions dim-cardActions' },
262
- h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
263
- busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
264
- h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) },
265
- '移除接入')))),
258
+ h('div', { className: 'dim-cardFooterLayout' },
259
+ h('div', { className: 'ddt-actions dim-cardActions' },
260
+ h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
261
+ busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
262
+ h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) },
263
+ '移除接入')),
264
+ summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
265
+ feedback ? h('div', {
266
+ className: 'ddt-summary dim-cardFeedback',
267
+ role: 'status',
268
+ }, feedback) : null))),
266
269
  removing ? h(RemoveConfirmation, {
267
270
  account,
268
271
  busy: busy === 'delete',
@@ -273,8 +276,11 @@ export function AccountCard({
273
276
 
274
277
  function AccountList(props) {
275
278
  return h('section', { className: 'dim-listSection' },
276
- h('div', { className: 'ddt-listHeading dim-listHeading' },
277
- h('h3', null, '已接入的钉钉机器人')),
279
+ h(ChannelListHeading, {
280
+ className: 'ddt-listHeading',
281
+ title: '已接入的钉钉机器人',
282
+ connectionLabel: 'Stream 长连接',
283
+ }),
278
284
  h('ul', { className: 'ddt-list dim-botList' }, props.bots.map((account) => h('li', { key: account.botId },
279
285
  h(AccountCard, {
280
286
  account,
@@ -86,10 +86,6 @@ const CSS = String.raw`
86
86
  .ddt-accountIdentity h3 { overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; }
87
87
  .ddt-accountIdentity p { margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; }
88
88
  .ddt-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; }
89
- .ddt-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; }
90
- .ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
91
- .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; }
92
- .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
93
89
  .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
94
90
  .ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; }
95
91
  .ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; }
@@ -110,8 +106,6 @@ const CSS = String.raw`
110
106
  .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; }
111
107
  .ddt-qrColumn { width: 100%; min-width: 0; }
112
108
  .ddt-qrCopy { width: 100%; }
113
- .ddt-metrics { gap: 8px; }
114
- .ddt-metric { padding: 10px; }
115
109
  }
116
110
  @media (max-width: 720px) {
117
111
  .ddt-heading, .ddt-accountTop { flex-direction: column; align-items: stretch; }
@@ -23,6 +23,7 @@ import {
23
23
  EMPTY_AGENT_PRESET_CATALOG,
24
24
  } from "../../agent-preset.js";
25
25
  import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
26
+ import { BotStatusMeta, ChannelListHeading } from "../../channel-card-meta.js";
26
27
  import { installFeishuStyles } from "./styles.js";
27
28
 
28
29
  export const name = "feishu-settings";
@@ -591,15 +592,15 @@ export function BotCard({
591
592
  h("h3", { id: titleId, title: bot.name }, bot.name),
592
593
  h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")),
593
594
  ),
594
- h("div", { className: "bxf-healthPill dim-botHealth", "data-health": stateForDisplay },
595
- h("span", { className: "bxf-dot dim-healthDot", "data-tone": tone }),
596
- h("span", null, HEALTH_LABELS[stateForDisplay] ?? "状态未知")),
597
- ),
598
- h("dl", { className: "bxf-statusGrid dim-botMetrics" },
599
- h("div", { className: "bxf-metric dim-botMetric" }, h("dt", null, "消息通道"),
600
- h("dd", null, connected ? "长连接" : stateForDisplay === "connecting" ? "连接中" : "已断开")),
601
- h("div", { className: "bxf-metric dim-botMetric" }, h("dt", null, "最近检查"),
602
- h("dd", null, formatCheckedTime(health.lastCheckedAt))),
595
+ h(BotStatusMeta, {
596
+ className: "bxf-healthPill",
597
+ dotClassName: "bxf-dot",
598
+ tone,
599
+ stateLabel: HEALTH_LABELS[stateForDisplay] ?? "状态未知",
600
+ lastCheckedAt: health.lastCheckedAt,
601
+ formatCheckedTime,
602
+ healthState: stateForDisplay,
603
+ }),
603
604
  ),
604
605
  h(WorkspaceEditor, {
605
606
  workspace: connection.workspace,
@@ -629,30 +630,31 @@ export function BotCard({
629
630
  }, provisionContent)
630
631
  : null,
631
632
  h("div", { className: "bxf-connectedFooter dim-cardFooter" },
632
- summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
633
- summary) : null,
634
- testNotice ? h("div", {
635
- className: "bxf-healthSummary dim-cardSummary",
636
- role: "status",
637
- }, testNotice) : null,
638
- h("div", { className: "bxf-actions bxf-botActions dim-cardActions" },
639
- h(Button, {
640
- className: "dim-cardAction", onClick: onReconnect,
641
- disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
642
- "aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
643
- }, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
644
- h(Button, {
645
- className: "bxf-repairButton dim-cardAction",
646
- onClick: onRepairCallback,
647
- disabled: Boolean(busy) || repairDisabled,
648
- "aria-busy": busy === "callback-repair" ? "true" : undefined,
649
- "aria-label": `修复${bot.name}的卡片按钮`,
650
- }, busy === "callback-repair" ? "等待扫码…" : "修复卡片按钮"),
651
- h(Button, {
652
- className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
653
- disabled: Boolean(busy), ref: removeButtonRef,
654
- "aria-label": `从 DeepSeek Harness 移除${bot.name}`,
655
- }, "移除接入")),
633
+ h("div", { className: "dim-cardFooterLayout" },
634
+ h("div", { className: "bxf-actions bxf-botActions dim-cardActions" },
635
+ h(Button, {
636
+ className: "dim-cardAction", onClick: onReconnect,
637
+ disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
638
+ "aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
639
+ }, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
640
+ h(Button, {
641
+ className: "bxf-repairButton dim-cardAction",
642
+ onClick: onRepairCallback,
643
+ disabled: Boolean(busy) || repairDisabled,
644
+ "aria-busy": busy === "callback-repair" ? "true" : undefined,
645
+ "aria-label": `修复${bot.name}的卡片按钮`,
646
+ }, busy === "callback-repair" ? "等待扫码…" : "修复卡片按钮"),
647
+ h(Button, {
648
+ className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
649
+ disabled: Boolean(busy), ref: removeButtonRef,
650
+ "aria-label": `从 DeepSeek Harness 移除${bot.name}`,
651
+ }, "移除接入")),
652
+ summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
653
+ summary) : null,
654
+ testNotice ? h("div", {
655
+ className: "bxf-healthSummary dim-cardFeedback",
656
+ role: "status",
657
+ }, testNotice) : null),
656
658
  ),
657
659
  ),
658
660
  removing
@@ -668,8 +670,12 @@ export function BotCard({
668
670
 
669
671
  function BotList(props) {
670
672
  return h("section", { className: "bxf-listSection dim-listSection", "aria-labelledby": "bxf-bot-list-title" },
671
- h("div", { className: "bxf-listHeading dim-listHeading" },
672
- h("h3", { id: "bxf-bot-list-title" }, "已接入的机器人")),
673
+ h(ChannelListHeading, {
674
+ className: "bxf-listHeading",
675
+ id: "bxf-bot-list-title",
676
+ title: "已接入的机器人",
677
+ connectionLabel: "长连接",
678
+ }),
673
679
  h("ul", { className: "bxf-botList dim-botList", role: "list" },
674
680
  props.bots.map((bot) => h("li", { key: bot.botId },
675
681
  h(BotCard, {
@@ -402,10 +402,6 @@ const CSS = String.raw`
402
402
  .bxf-healthPill[data-health="degraded"], .bxf-healthPill[data-health="checking"], .bxf-healthPill[data-health="connecting"] { color: var(--bxf-warning); background: color-mix(in srgb, var(--bxf-warning) 10%, transparent); }
403
403
  .bxf-healthPill[data-health="offline"], .bxf-healthPill[data-health="error"] { color: var(--bxf-error); background: color-mix(in srgb, var(--bxf-error) 10%, transparent); }
404
404
 
405
- .bxf-statusGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 22px; }
406
- .bxf-metric { min-width: 0; border: 1px solid var(--dsw-alias-border-l2, #dee0e3); border-radius: 9px; padding: 12px 13px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
407
- .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; }
408
- .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; }
409
405
 
410
406
  .bxf-responseMode { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; align-items: center; column-gap: 10px; row-gap: 5px; margin-top: 6px; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
411
407
  .bxf-responseModeHeader { display: contents; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; }
@@ -517,7 +513,6 @@ const CSS = String.raw`
517
513
  .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; }
518
514
  .bxf-botProvision .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); }
519
515
  .bxf-qrCopy { width: 100%; }
520
- .bxf-statusGrid { grid-template-columns: minmax(0, 1fr); }
521
516
  .bxf-connectedTop { align-items: flex-start; flex-direction: column; }
522
517
  .bxf-inlineError { grid-template-columns: minmax(0, 1fr); padding: 20px; }
523
518
  .bxf-statusNotice { align-items: flex-start; flex-wrap: wrap; }
@@ -10,6 +10,7 @@ import {
10
10
  EMPTY_AGENT_PRESET_CATALOG,
11
11
  } from '../../agent-preset.js';
12
12
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
13
+ import { BotStatusMeta, ChannelListHeading } from '../../channel-card-meta.js';
13
14
  import { installDingtalkStyles } from '../dingtalk/styles.js';
14
15
  import {
15
16
  QQ_ENDPOINTS,
@@ -176,11 +177,14 @@ export function AccountCard({
176
177
  h('div', { className: 'ddt-avatar dim-botAvatar dqq-avatar', 'aria-hidden': 'true' }, h(QqLogoGlyph, { size: 29 })),
177
178
  h('div', { className: 'dim-botName' },
178
179
  h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
179
- h('div', { className: 'ddt-health dim-botHealth' },
180
- h('span', { className: 'ddt-dot dim-healthDot', 'data-tone': tone }), h('span', null, stateLabel))),
181
- h('dl', { className: 'ddt-metrics dim-botMetrics' },
182
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'WebSocket 长连接' : '离线')),
183
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))),
180
+ h(BotStatusMeta, {
181
+ className: 'ddt-health',
182
+ dotClassName: 'ddt-dot',
183
+ tone,
184
+ stateLabel,
185
+ lastCheckedAt: account.health.lastCheckedAt,
186
+ formatCheckedTime: checkedTime,
187
+ })),
184
188
  h(WorkspaceEditor, {
185
189
  workspace: account.workspace,
186
190
  disabled: Boolean(busy),
@@ -192,15 +196,16 @@ export function AccountCard({
192
196
  onSave: onAgentPresetSave,
193
197
  }),
194
198
  h('div', { className: 'ddt-accountFooter dim-cardFooter' },
195
- summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
196
- feedback ? h('div', {
197
- className: 'ddt-summary dim-cardSummary',
198
- role: 'status',
199
- 'aria-live': 'polite',
200
- }, feedback) : null,
201
- h('div', { className: 'ddt-actions dim-cardActions' },
202
- h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
203
- h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
199
+ h('div', { className: 'dim-cardFooterLayout' },
200
+ h('div', { className: 'ddt-actions dim-cardActions' },
201
+ h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
202
+ h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
203
+ summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
204
+ feedback ? h('div', {
205
+ className: 'ddt-summary dim-cardFeedback',
206
+ role: 'status',
207
+ 'aria-live': 'polite',
208
+ }, feedback) : null))),
204
209
  removing ? h(RemoveConfirmation, {
205
210
  account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
206
211
  }) : null);
@@ -432,8 +437,11 @@ export function QqSettingsTab({ rpcCall }) {
432
437
 
433
438
  const botList = model.bots.length > 0
434
439
  ? h('section', { className: 'dim-listSection' },
435
- h('div', { className: 'ddt-listHeading dim-listHeading' },
436
- h('h3', null, '已绑定的 QQ 机器人')),
440
+ h(ChannelListHeading, {
441
+ className: 'ddt-listHeading',
442
+ title: '已绑定的 QQ 机器人',
443
+ connectionLabel: 'WebSocket 长连接',
444
+ }),
437
445
  h('ul', { className: 'ddt-list dim-botList' }, model.bots.map((account) =>
438
446
  h('li', { key: account.botId }, h(AccountCard, {
439
447
  account,
@@ -10,6 +10,7 @@ import {
10
10
  EMPTY_AGENT_PRESET_CATALOG,
11
11
  } from '../../agent-preset.js';
12
12
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
13
+ import { BotStatusMeta, ChannelListHeading } from '../../channel-card-meta.js';
13
14
 
14
15
  const Button = React.forwardRef(function Button(
15
16
  { children, kind = 'secondary', className = '', ...props },
@@ -82,14 +83,14 @@ export function createTokenChannelSettings(definition) {
82
83
  h(LogoGlyph, { size: 29 })),
83
84
  h('div', { className: 'dim-botName' },
84
85
  h('h3', null, account.bot.name), h('p', null, identity))),
85
- h('div', { className: 'ddt-health dim-botHealth' },
86
- h('span', { className: 'ddt-dot dim-healthDot', 'data-tone': tone }),
87
- h('span', null, stateLabel))),
88
- h('dl', { className: 'ddt-metrics dim-botMetrics' },
89
- h('div', { className: 'ddt-metric dim-botMetric' },
90
- h('dt', null, '消息通道'), h('dd', null, account.connected ? connectionLabel : '离线')),
91
- h('div', { className: 'ddt-metric dim-botMetric' },
92
- h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))),
86
+ h(BotStatusMeta, {
87
+ className: 'ddt-health',
88
+ dotClassName: 'ddt-dot',
89
+ tone,
90
+ stateLabel,
91
+ lastCheckedAt: account.health.lastCheckedAt,
92
+ formatCheckedTime: checkedTime,
93
+ })),
93
94
  h(WorkspaceEditor, {
94
95
  workspace: account.workspace,
95
96
  disabled: Boolean(busy),
@@ -106,20 +107,24 @@ export function createTokenChannelSettings(definition) {
106
107
  onSave: onAccountSettingsSave,
107
108
  }) : null,
108
109
  h('div', { className: 'ddt-accountFooter dim-cardFooter' },
109
- summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
110
- testNotice ? h('div', { className: 'ddt-summary dim-cardSummary', role: 'status' }, testNotice) : null,
111
- h('div', { className: 'ddt-actions dim-cardActions' },
112
- h(Button, {
113
- className: 'dim-cardAction',
114
- onClick: onReconnect,
115
- disabled: Boolean(busy),
116
- }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
117
- h(Button, {
118
- className: 'dim-cardAction',
119
- kind: 'danger',
120
- onClick: onRequestRemove,
121
- disabled: Boolean(busy),
122
- }, '移除接入')))),
110
+ h('div', { className: 'dim-cardFooterLayout' },
111
+ h('div', { className: 'ddt-actions dim-cardActions' },
112
+ h(Button, {
113
+ className: 'dim-cardAction',
114
+ onClick: onReconnect,
115
+ disabled: Boolean(busy),
116
+ }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
117
+ h(Button, {
118
+ className: 'dim-cardAction',
119
+ kind: 'danger',
120
+ onClick: onRequestRemove,
121
+ disabled: Boolean(busy),
122
+ }, '移除接入')),
123
+ summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
124
+ testNotice ? h('div', {
125
+ className: 'ddt-summary dim-cardFeedback',
126
+ role: 'status',
127
+ }, testNotice) : null))),
123
128
  removing ? h('div', { className: 'ddt-confirm dim-confirm', role: 'alertdialog' },
124
129
  h('strong', null, `从 DeepSeek Harness 移除“${account.bot.name}”?`),
125
130
  h('p', null, `这会停止消息连接,并删除本机保存的 ${credentialNoun}、机器人配置及会话映射。${platformLabel}中的机器人不会被自动删除。`),
@@ -267,8 +272,11 @@ export function createTokenChannelSettings(definition) {
267
272
 
268
273
  const botList = model.bots.length > 0
269
274
  ? h('section', { className: 'dim-listSection' },
270
- h('div', { className: 'ddt-listHeading dim-listHeading' },
271
- h('h3', null, `已接入的 ${channel} 机器人`)),
275
+ h(ChannelListHeading, {
276
+ className: 'ddt-listHeading',
277
+ title: `已接入的 ${channel} 机器人`,
278
+ connectionLabel,
279
+ }),
272
280
  h('ul', { className: 'ddt-list dim-botList' }, model.bots.map((account) =>
273
281
  h('li', { key: account.botId }, h(AccountCard, {
274
282
  account,
@@ -10,6 +10,7 @@ import {
10
10
  EMPTY_AGENT_PRESET_CATALOG,
11
11
  } from '../../agent-preset.js';
12
12
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
13
+ import { BotStatusMeta, ChannelListHeading } from '../../channel-card-meta.js';
13
14
  import { installDingtalkStyles } from '../dingtalk/styles.js';
14
15
  import {
15
16
  WECOM_ENDPOINTS,
@@ -175,11 +176,14 @@ export function AccountCard({
175
176
  h('div', { className: 'ddt-avatar dim-botAvatar dwecom-avatar', 'aria-hidden': 'true' }, h(WecomLogoGlyph, { size: 29 })),
176
177
  h('div', { className: 'dim-botName' },
177
178
  h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
178
- h('div', { className: 'ddt-health dim-botHealth' },
179
- h('span', { className: 'ddt-dot dim-healthDot', 'data-tone': tone }), h('span', null, stateLabel))),
180
- h('dl', { className: 'ddt-metrics dim-botMetrics' },
181
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'WebSocket 长连接' : '离线')),
182
- h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))),
179
+ h(BotStatusMeta, {
180
+ className: 'ddt-health',
181
+ dotClassName: 'ddt-dot',
182
+ tone,
183
+ stateLabel,
184
+ lastCheckedAt: account.health.lastCheckedAt,
185
+ formatCheckedTime: checkedTime,
186
+ })),
183
187
  h(WorkspaceEditor, {
184
188
  workspace: account.workspace,
185
189
  disabled: Boolean(busy),
@@ -191,15 +195,16 @@ export function AccountCard({
191
195
  onSave: onAgentPresetSave,
192
196
  }),
193
197
  h('div', { className: 'ddt-accountFooter dim-cardFooter' },
194
- summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
195
- feedback ? h('div', {
196
- className: 'ddt-summary dim-cardSummary',
197
- role: 'status',
198
- 'aria-live': 'polite',
199
- }, feedback) : null,
200
- h('div', { className: 'ddt-actions dim-cardActions' },
201
- h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
202
- h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
198
+ h('div', { className: 'dim-cardFooterLayout' },
199
+ h('div', { className: 'ddt-actions dim-cardActions' },
200
+ h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
201
+ h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
202
+ summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
203
+ feedback ? h('div', {
204
+ className: 'ddt-summary dim-cardFeedback',
205
+ role: 'status',
206
+ 'aria-live': 'polite',
207
+ }, feedback) : null))),
203
208
  removing ? h(RemoveConfirmation, {
204
209
  account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
205
210
  }) : null);
@@ -463,8 +468,11 @@ export function WecomSettingsTab({ rpcCall }) {
463
468
 
464
469
  const botList = model.bots.length > 0
465
470
  ? h('section', { className: 'dim-listSection' },
466
- h('div', { className: 'ddt-listHeading dim-listHeading' },
467
- h('h3', null, '已绑定的企业微信机器人')),
471
+ h(ChannelListHeading, {
472
+ className: 'ddt-listHeading',
473
+ title: '已绑定的企业微信机器人',
474
+ connectionLabel: 'WebSocket 长连接',
475
+ }),
468
476
  h('ul', { className: 'ddt-list dim-botList' }, model.bots.map((account) =>
469
477
  h('li', { key: account.botId }, h(AccountCard, {
470
478
  account,
@@ -22,6 +22,7 @@ import {
22
22
  EMPTY_AGENT_PRESET_CATALOG,
23
23
  } from '../../agent-preset.js';
24
24
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
25
+ import { BotStatusMeta, ChannelListHeading } from '../../channel-card-meta.js';
25
26
  import { installWeixinStyles } from './styles.js';
26
27
 
27
28
  export const name = 'weixin-settings';
@@ -221,14 +222,14 @@ export function AccountCard({
221
222
  h('div', { className: 'dxw-accountIdentity dim-botIdentity' },
222
223
  h('div', { className: 'dxw-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })),
223
224
  h('div', { className: 'dim-botName' }, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))),
224
- h('div', { className: 'dxw-health dim-botHealth' },
225
- h('span', { className: 'dxw-dot dim-healthDot', 'data-tone': tone }),
226
- h('span', null, account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪'))),
227
- h('dl', { className: 'dxw-metrics dim-botMetrics' },
228
- h('div', { className: 'dxw-metric dim-botMetric' }, h('dt', null, '消息通道'),
229
- h('dd', null, account.connected ? 'iLink 长轮询' : '离线')),
230
- h('div', { className: 'dxw-metric dim-botMetric' }, h('dt', null, '最近检查'),
231
- h('dd', null, checkedTime(account.health.lastCheckedAt)))),
225
+ h(BotStatusMeta, {
226
+ className: 'dxw-health',
227
+ dotClassName: 'dxw-dot',
228
+ tone,
229
+ stateLabel: account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪',
230
+ lastCheckedAt: account.health.lastCheckedAt,
231
+ formatCheckedTime: checkedTime,
232
+ })),
232
233
  h(WorkspaceEditor, {
233
234
  workspace: account.workspace,
234
235
  disabled: Boolean(busy),
@@ -240,20 +241,21 @@ export function AccountCard({
240
241
  onSave: onAgentPresetSave,
241
242
  }),
242
243
  h('div', { className: 'dxw-accountFooter dim-cardFooter' },
243
- summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
244
- account.lastMessageError ? h('div', {
245
- className: 'dxw-summary dim-cardSummary',
246
- role: 'status',
247
- }, `最近一条消息处理失败:${account.lastMessageError.message}`) : null,
248
- feedback ? h('div', {
249
- className: 'dxw-summary dim-cardSummary',
250
- role: 'status',
251
- 'aria-live': 'polite',
252
- }, feedback) : null,
253
- h('div', { className: 'dxw-actions dim-cardActions' },
254
- h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
255
- busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
256
- h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
244
+ h('div', { className: 'dim-cardFooterLayout' },
245
+ h('div', { className: 'dxw-actions dim-cardActions' },
246
+ h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
247
+ busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
248
+ h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
249
+ summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
250
+ account.lastMessageError ? h('div', {
251
+ className: 'dxw-summary dim-cardSummary',
252
+ role: 'status',
253
+ }, `最近一条消息处理失败:${account.lastMessageError.message}`) : null,
254
+ feedback ? h('div', {
255
+ className: 'dxw-summary dim-cardFeedback',
256
+ role: 'status',
257
+ 'aria-live': 'polite',
258
+ }, feedback) : null))),
257
259
  removing ? h('div', { className: 'dxw-confirm dim-confirm', role: 'alertdialog' },
258
260
  h('strong', null, '从此 Harness 移除这个微信账号?'),
259
261
  h('p', null, '这会停止消息连接,并删除本机保存的 bot_token、账号配置和会话映射。其他微信账号不受影响。'),
@@ -266,7 +268,11 @@ export function AccountCard({
266
268
 
267
269
  function AccountList(props) {
268
270
  return h('section', { className: 'dim-listSection' },
269
- h('div', { className: 'dxw-listHeading dim-listHeading' }, h('h3', null, '已接入的微信账号')),
271
+ h(ChannelListHeading, {
272
+ className: 'dxw-listHeading',
273
+ title: '已接入的微信账号',
274
+ connectionLabel: 'iLink 长轮询',
275
+ }),
270
276
  h('ul', { className: 'dxw-list dim-botList' }, props.bots.map((account) => h('li', { key: account.botId },
271
277
  h(AccountCard, {
272
278
  account,