@xmanrui/dsh-im 2.6.0 → 3.0.1

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": "2.6.0",
3
+ "version": "3.0.1",
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",
@@ -83,7 +83,7 @@
83
83
  "scripts": {
84
84
  "build": "node plugin-src/client/build.mjs && node plugin-src/host/build.mjs",
85
85
  "test": "node --test test/*.test.mjs test/channels/*/*.test.mjs",
86
- "check": "npm test && npm run build && node scripts/verify-package.mjs"
86
+ "check": "npm run build && npm test && node scripts/verify-package.mjs"
87
87
  },
88
88
  "engines": {
89
89
  "node": ">=22.19"
@@ -16,7 +16,6 @@ import {
16
16
  } from '../../channel-card-meta.js';
17
17
  import {
18
18
  DINGTALK_ENDPOINTS,
19
- DINGTALK_RPC_CHANNEL,
20
19
  connectionTestFeedback,
21
20
  formatRemaining,
22
21
  normalizeProvisioning,
@@ -29,9 +28,6 @@ import { installDingtalkStyles } from './styles.js';
29
28
 
30
29
  const ACTIVE_PROVISION_STATES = new Set(['pending', 'scanned', 'authorizing', 'creating', 'connecting']);
31
30
 
32
- export const name = 'dingtalk-settings';
33
- export const inject = ['slots', 'connection'];
34
-
35
31
  function DingtalkIcon({ size = 28 }) {
36
32
  return h('svg', {
37
33
  width: size,
@@ -864,16 +860,3 @@ export function DingtalkSettingsTab({ rpcCall }) {
864
860
  })
865
861
  : null)));
866
862
  }
867
-
868
- export function apply(ctx) {
869
- ctx.effect(() => installDingtalkStyles(), 'dingtalk-settings: install client styles');
870
- const rpcCall = (endpoint, payload, signal) =>
871
- ctx.connection.rpc.call(DINGTALK_RPC_CHANNEL, endpoint, payload, signal);
872
- ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
873
- name: 'settings.plugins.tab',
874
- id: 'dingtalk',
875
- order: 40,
876
- label: '钉钉',
877
- inject: () => ({ rpcCall }),
878
- }, DingtalkSettingsTab));
879
- }
@@ -1,4 +1,4 @@
1
- export const DINGTALK_STYLE_ID = 'xmanrui-dsh-dingtalk-settings';
1
+ export const DINGTALK_STYLE_ID = 'xmanrui-dsh-im-dingtalk-settings';
2
2
 
3
3
  const CSS = String.raw`
4
4
  .ddt-page {
@@ -126,7 +126,7 @@ export function installDingtalkStyles() {
126
126
  const existing = document.querySelector(`style[data-plugin-css="${DINGTALK_STYLE_ID}"]`);
127
127
  if (existing) return () => {};
128
128
  const style = document.createElement('style');
129
- style.dataset.plugin = '@xmanrui/dsh-dingtalk';
129
+ style.dataset.plugin = '@xmanrui/dsh-im';
130
130
  style.dataset.pluginCss = DINGTALK_STYLE_ID;
131
131
  style.textContent = CSS;
132
132
  document.head.appendChild(style);
@@ -6,7 +6,6 @@ import { h } from "../../i18n.js";
6
6
  import {
7
7
  FEISHU_ENDPOINTS,
8
8
  FEISHU_REGISTRATION_OPERATIONS,
9
- FEISHU_RPC_CHANNEL,
10
9
  formatRemaining,
11
10
  normalizeBotsSnapshot,
12
11
  normalizeGroupResponseMode,
@@ -28,11 +27,6 @@ import {
28
27
  ChannelListHeading,
29
28
  LastMessageErrorSummary,
30
29
  } from "../../channel-card-meta.js";
31
- import { installFeishuStyles } from "./styles.js";
32
-
33
- export const name = "feishu-settings";
34
- export const inject = ["slots", "connection"];
35
-
36
30
  const CALLBACK_REPAIR_OPERATION = FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR;
37
31
  const GROUP_MESSAGE_PERMISSION_OPERATION = FEISHU_REGISTRATION_OPERATIONS.GROUP_MESSAGE_PERMISSION;
38
32
 
@@ -1540,26 +1534,3 @@ export function FeishuSettingsTab({ rpcCall }) {
1540
1534
  ),
1541
1535
  ));
1542
1536
  }
1543
-
1544
- export function apply(ctx) {
1545
- ctx.effect(
1546
- () => installFeishuStyles(),
1547
- "feishu-settings: install client styles",
1548
- );
1549
-
1550
- const rpcCall = (endpoint, payload, signal) =>
1551
- ctx.connection.rpc.call(FEISHU_RPC_CHANNEL, endpoint, payload, signal);
1552
-
1553
- ctx.slots.inject("settings.plugins.tab", () =>
1554
- ctx.slots.register(
1555
- {
1556
- name: "settings.plugins.tab",
1557
- id: "feishu",
1558
- order: 20,
1559
- label: "飞书",
1560
- inject: () => ({ rpcCall }),
1561
- },
1562
- FeishuSettingsTab,
1563
- ),
1564
- );
1565
- }
@@ -1,4 +1,4 @@
1
- export const FEISHU_STYLE_ID = "beihuixinghe-dsh-feishu-settings";
1
+ export const FEISHU_STYLE_ID = "xmanrui-dsh-im-feishu-settings";
2
2
 
3
3
  const CSS = String.raw`
4
4
  .bxf-page {
@@ -562,7 +562,7 @@ export function installFeishuStyles() {
562
562
  }
563
563
 
564
564
  const style = document.createElement("style");
565
- style.dataset.plugin = "@xmanrui/dsh-feishu";
565
+ style.dataset.plugin = "@xmanrui/dsh-im";
566
566
  style.dataset.pluginCss = FEISHU_STYLE_ID;
567
567
  style.textContent = CSS;
568
568
  document.head.appendChild(style);
@@ -18,7 +18,6 @@ import {
18
18
  import { installDingtalkStyles } from '../dingtalk/styles.js';
19
19
  import {
20
20
  QQ_ENDPOINTS,
21
- QQ_RPC_CHANNEL,
22
21
  connectionTestFeedback,
23
22
  formatRemaining,
24
23
  normalizeProvisioning,
@@ -514,11 +513,3 @@ export function QqSettingsTab({ rpcCall }) {
514
513
  ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
515
514
  botList)));
516
515
  }
517
-
518
- export function apply(ctx) {
519
- ctx.effect(() => installQqStyles(), 'qq-settings: install client styles');
520
- const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(QQ_RPC_CHANNEL, endpoint, payload, signal);
521
- ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
522
- name: 'settings.plugins.tab', id: 'qq', order: 50, label: 'QQ', inject: () => ({ rpcCall }),
523
- }, QqSettingsTab));
524
- }
@@ -4,32 +4,34 @@ const CSS = String.raw`
4
4
  .dtg-page { --ddt-accent: #229ed9; --ddt-accent-deep: #1687bd; --ddt-accent-wash: #eaf7fd; }
5
5
  .dtg-avatar { color: #fff; background: #229ed9; }
6
6
  .dtg-avatar svg { display: block; }
7
- .dtg-access { display: grid; gap: 10px; padding: 12px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 10px; background: var(--dsw-alias-bg-layer-2, #f7f8fa); }
8
- .dtg-accessHeading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
9
- .dtg-accessHeading > strong { font-size: 13px; }
10
- .dtg-accessStatus { min-width: 0; display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
11
- .dtg-accessBadge { flex: none; padding: 3px 8px; border-radius: 999px; color: #1687bd; background: #eaf7fd; font-size: 11px; font-weight: 700; }
7
+ .dtg-access { min-width: 0; width: 100%; max-width: 100%; display: grid; gap: 10px; padding: 12px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 10px; background: var(--dsw-alias-bg-layer-2, #f7f8fa); }
8
+ .dtg-accessHeading { position: relative; min-width: 0; max-width: 100%; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 12px; }
9
+ .dtg-accessHeading > strong { min-width: 0; font-size: 13px; overflow-wrap: anywhere; }
10
+ .dtg-accessStatus { min-width: 0; max-width: 100%; flex: 0 1 auto; display: inline-flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 6px; }
11
+ .dtg-accessBadge { min-width: 0; max-width: 100%; flex: 0 1 auto; padding: 3px 8px; border-radius: 999px; color: #1687bd; background: #eaf7fd; font-size: 11px; font-weight: 700; overflow-wrap: anywhere; text-align: center; }
12
12
  .dtg-accessBadge[data-mode="private-allowlist"] { color: #a15c00; background: #fff3d6; }
13
- .dtg-accessHelp { position: relative; display: inline-flex; flex: none; }
13
+ .dtg-accessHelp { position: static; display: inline-flex; flex: none; }
14
14
  .dtg-accessHelpButton { width: 20px; height: 20px; display: grid; place-items: center; padding: 0; border: 1px solid color-mix(in srgb, #229ed9 28%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 50%; color: #1687bd; background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; line-height: 1; font-weight: 750; cursor: help; transition: border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease; }
15
15
  .dtg-accessHelpButton:hover { border-color: #229ed9; color: #1178a8; background: #eaf7fd; }
16
16
  .dtg-accessHelpButton:focus-visible { outline: none; border-color: #229ed9; box-shadow: 0 0 0 3px color-mix(in srgb, #229ed9 18%, transparent); }
17
- .dtg-accessTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 30; width: 260px; max-width: min(280px, calc(100vw - 48px)); display: grid; gap: 8px; padding: 10px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
17
+ .dtg-accessTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 30; width: min(300px, 100%); max-width: 100%; display: grid; gap: 8px; padding: 10px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
18
18
  .dtg-accessTooltipItem { display: grid; gap: 2px; }
19
19
  .dtg-accessTooltipItem + .dtg-accessTooltipItem { padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l2, #eef0f3); }
20
20
  .dtg-accessTooltipItem strong { color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 17px; font-weight: 700; }
21
21
  .dtg-accessTooltipItem > span { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 16px; font-weight: 400; }
22
22
  .dtg-accessHelp:hover .dtg-accessTooltip, .dtg-accessHelp:focus-within .dtg-accessTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
23
- .dtg-accessField { display: grid; gap: 5px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; font-weight: 600; }
24
- .dtg-accessField select, .dtg-accessField textarea { width: 100%; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l1, #c9cdd4); border-radius: 7px; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-weight: 400; }
23
+ .dtg-accessField { min-width: 0; max-width: 100%; display: grid; gap: 5px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; font-weight: 600; }
24
+ .dtg-accessField select, .dtg-accessField textarea { min-width: 0; width: 100%; max-width: 100%; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l1, #c9cdd4); border-radius: 7px; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-weight: 400; }
25
25
  .dtg-accessField select { height: 34px; padding: 0 9px; }
26
26
  .dtg-accessField textarea { min-height: 68px; padding: 8px 9px; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
27
27
  .dtg-accessField textarea:disabled { color: var(--dsw-alias-label-tertiary, #8f959e); background: var(--dsw-alias-bg-module-platform, #f2f3f5); cursor: not-allowed; resize: none; opacity: 1; }
28
+ .dtg-accessField > span, .dtg-accessField small { overflow-wrap: anywhere; }
28
29
  .dtg-accessField small { color: var(--dsw-alias-label-secondary, #646a73); font-weight: 400; }
29
30
  .dtg-accessWarning, .dtg-accessError { margin: 0; font-size: 12px; line-height: 1.5; }
30
31
  .dtg-accessWarning { color: #a15c00; }
31
32
  .dtg-accessError { color: var(--dsw-alias-state-error-primary, #d83931); }
32
- .dtg-accessActions { display: flex; justify-content: flex-end; }
33
+ .dtg-accessActions { min-width: 0; max-width: 100%; display: flex; justify-content: flex-end; flex-wrap: wrap; }
34
+ .dtg-accessActions .ddt-button { max-width: 100%; white-space: normal; }
33
35
  `;
34
36
 
35
37
  export function installTelegramStyles() {
@@ -18,7 +18,6 @@ import {
18
18
  import { installDingtalkStyles } from '../dingtalk/styles.js';
19
19
  import {
20
20
  WECOM_ENDPOINTS,
21
- WECOM_RPC_CHANNEL,
22
21
  formatRemaining,
23
22
  normalizeProvisioning,
24
23
  normalizeSnapshot,
@@ -546,11 +545,3 @@ export function WecomSettingsTab({ rpcCall }) {
546
545
  ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
547
546
  botList)));
548
547
  }
549
-
550
- export function apply(ctx) {
551
- ctx.effect(() => installWecomStyles(), 'wecom-settings: install client styles');
552
- const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(WECOM_RPC_CHANNEL, endpoint, payload, signal);
553
- ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
554
- name: 'settings.plugins.tab', id: 'wecom', order: 45, label: '企业微信', inject: () => ({ rpcCall }),
555
- }, WecomSettingsTab));
556
- }
@@ -5,7 +5,6 @@ import { QrActionIcon } from '../../credential-binding.js';
5
5
  import { h } from '../../i18n.js';
6
6
  import {
7
7
  WEIXIN_ENDPOINTS,
8
- WEIXIN_RPC_CHANNEL,
9
8
  formatRemaining,
10
9
  normalizeProvisioning,
11
10
  normalizeSnapshot,
@@ -27,11 +26,6 @@ import {
27
26
  ChannelListHeading,
28
27
  LastMessageErrorSummary,
29
28
  } from '../../channel-card-meta.js';
30
- import { installWeixinStyles } from './styles.js';
31
-
32
- export const name = 'weixin-settings';
33
- export const inject = ['slots', 'connection'];
34
-
35
29
  const Button = React.forwardRef(function Button(
36
30
  { children, kind = 'secondary', className = '', ...props },
37
31
  ref,
@@ -717,16 +711,3 @@ export function WeixinSettingsTab({ rpcCall }) {
717
711
  : null),
718
712
  ));
719
713
  }
720
-
721
- export function apply(ctx) {
722
- ctx.effect(() => installWeixinStyles(), 'weixin-settings: install client styles');
723
- const rpcCall = (endpoint, payload, signal) =>
724
- ctx.connection.rpc.call(WEIXIN_RPC_CHANNEL, endpoint, payload, signal);
725
- ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
726
- name: 'settings.plugins.tab',
727
- id: 'weixin',
728
- order: 30,
729
- label: '微信',
730
- inject: () => ({ rpcCall }),
731
- }, WeixinSettingsTab));
732
- }
@@ -1,4 +1,4 @@
1
- export const WEIXIN_STYLE_ID = 'xmanrui-dsh-weixin-settings';
1
+ export const WEIXIN_STYLE_ID = 'xmanrui-dsh-im-weixin-settings';
2
2
 
3
3
  const CSS = String.raw`
4
4
  .dxw-page {
@@ -107,7 +107,7 @@ export function installWeixinStyles() {
107
107
  const existing = document.querySelector(`style[data-plugin-css="${WEIXIN_STYLE_ID}"]`);
108
108
  if (existing) return () => {};
109
109
  const style = document.createElement('style');
110
- style.dataset.plugin = '@xmanrui/dsh-weixin';
110
+ style.dataset.plugin = '@xmanrui/dsh-im';
111
111
  style.dataset.pluginCss = WEIXIN_STYLE_ID;
112
112
  style.textContent = CSS;
113
113
  document.head.appendChild(style);
@@ -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 触手可及': 'DeepSeek Harness, always within reach',
10
+ '让 DeepSeek Harness 触手可及': 'Connecting DeepSeek Harness',
11
11
  '当前版本': 'Current version',
12
12
  'AI Office': 'AI Office',
13
13
  '(实验功能)': '(Experimental)',
@@ -517,6 +517,9 @@ const EN = Object.freeze({
517
517
  'QQ 服务没有返回扫码绑定进度': 'QQ did not return QR setup progress',
518
518
  'QQ 扫码服务没有返回有效的绑定任务': 'QQ did not return a valid setup attempt',
519
519
  'QQ WebSocket 长连接运行正常': 'QQ WebSocket connection is healthy',
520
+ 'QQ 连接未就绪,插件会自动重试。': 'The QQ connection is not ready; the plugin will retry automatically.',
521
+ 'QQ 连接未就绪,插件会自动重试': 'The QQ connection is not ready; the plugin will retry automatically',
522
+ 'QQ 连接当前离线': 'The QQ connection is currently offline',
520
523
  'QQ 服务没有返回有效的机器人列表': 'QQ did not return a valid bot list',
521
524
  '尚未绑定 QQ 机器人': 'No QQ bot connected yet',
522
525
  '用于绑定 QQ 机器人的一次性二维码': 'One-time QR code for connecting a QQ bot',
@@ -333,10 +333,10 @@ export function apply(ctx) {
333
333
  pickDirectory: () => ctx.workspaces.pickDirectory(),
334
334
  });
335
335
 
336
- ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
337
- name: 'settings.plugins.tab',
338
- id: 'im',
339
- order: 20,
336
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
337
+ name: 'settings.section',
338
+ id: 'xmanrui-dsh-im',
339
+ order: 21,
340
340
  label: () => t('IM机器人'),
341
341
  locale: IM_LOCALE_NAMESPACE,
342
342
  inject: () => ({
@@ -16,7 +16,7 @@ const CSS = String.raw`
16
16
  .dim-brand:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
17
17
  .dim-brandName { color: var(--dsw-alias-label-primary, #1f2329); font-size: 20px; line-height: 24px; font-weight: 800; letter-spacing: .04em; }
18
18
  .dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 500; white-space: nowrap; }
19
- .dim-versionTooltip { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; width: max-content; max-width: min(220px, 80vw); display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
19
+ .dim-versionTooltip { position: absolute; top: calc(100% + 8px); left: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
20
20
  .dim-versionTooltip strong { color: var(--dsw-alias-label-primary, #1f2329); font: 600 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace; }
21
21
  .dim-brand:hover .dim-versionTooltip, .dim-brand:focus .dim-versionTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
22
22
  .dim-githubAction { position: relative; display: inline-flex; flex: none; }
@@ -24,7 +24,7 @@ const CSS = String.raw`
24
24
  .dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
25
25
  .dim-githubLink:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
26
26
  .dim-githubArrow { font-size: 13px; line-height: 1; }
27
- .dim-githubTooltip { position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
27
+ .dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
28
28
  .dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
29
29
  .dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; }
30
30
  .dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 1px 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; }
@@ -81,7 +81,7 @@ const CSS = String.raw`
81
81
  .dim-panel .dim-credentialButton:hover:not(:disabled) { border-color: #4e5969; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
82
82
  .dim-panel .dim-credentialButton[aria-pressed="true"] { border-color: #4e5969; background: var(--dsw-alias-bg-module-platform, #f2f3f5); box-shadow: inset 0 0 0 1px rgb(78 89 105 / 8%); }
83
83
  .dim-panel .bxf-headingTools .dim-onlineBadge, .dim-panel .dxw-tools .dim-onlineBadge, .dim-panel .ddt-tools .dim-onlineBadge { min-height: 30px; display: inline-flex; align-items: center; justify-self: end; gap: 0; padding: 0 11px; border: 0; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f2f3f5); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; }
84
- .dim-panel .dim-channelPage { width: 100%; max-width: none; display: flex; flex-direction: column; gap: 12px; padding: 0 0 24px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; }
84
+ .dim-panel .dim-channelPage { min-width: 0; width: 100%; max-width: none; display: flex; flex-direction: column; gap: 12px; padding: 0 0 24px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; }
85
85
  .dim-panel .dim-surfaceCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
86
86
  .dim-panel .dim-surfaceCard::before { display: none; }
87
87
  .dim-panel .dim-surfaceBody { padding: 24px; }
@@ -96,7 +96,7 @@ const CSS = String.raw`
96
96
  .dim-panel .dim-credentialError, .dim-panel .dim-credentialActions { grid-column: 1 / -1; }
97
97
  .dim-panel .dim-credentialError { margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.5; }
98
98
  .dim-panel .dim-credentialActions { margin-top: 0; }
99
- .dim-panel .dim-listSection { display: flex; flex-direction: column; gap: 0; }
99
+ .dim-panel .dim-listSection { min-width: 0; width: 100%; max-width: 100%; display: flex; flex-direction: column; gap: 0; }
100
100
  .dim-panel .dim-listHeading { min-height: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 6px; padding: 0; }
101
101
  .dim-panel .dim-listHeading h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: normal; font-weight: 650; }
102
102
  .dim-panel .dim-listTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
@@ -107,7 +107,8 @@ const CSS = String.raw`
107
107
  .dim-panel .dim-channelTooltip { position: absolute; top: calc(100% + 7px); left: 0; z-index: 30; width: max-content; max-width: min(280px, calc(100vw - 48px)); display: flex; align-items: baseline; gap: 5px; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); font-size: 11px; line-height: 16px; font-weight: 400; white-space: normal; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
108
108
  .dim-panel .dim-channelTooltip strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 600; white-space: nowrap; }
109
109
  .dim-panel .dim-channelHelp:hover .dim-channelTooltip, .dim-panel .dim-channelHelp:focus-within .dim-channelTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
110
- .dim-panel .dim-botList { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
110
+ .dim-panel .dim-botList { min-width: 0; width: 100%; max-width: 100%; display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; margin: 0; padding: 0; list-style: none; }
111
+ .dim-panel .dim-botList > li { min-width: 0; max-width: 100%; }
111
112
  .dim-panel .dim-loadingView { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; }
112
113
  .dim-panel .dim-loadingView h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; line-height: normal; font-weight: 650; }
113
114
  .dim-panel .dim-loadingView p { margin: 0; line-height: 1.6; }
@@ -161,7 +162,7 @@ const CSS = String.raw`
161
162
  .dim-panel .dim-workspaceHeader > span { grid-column: 1; grid-row: 1; white-space: nowrap; }
162
163
  .dim-panel .dim-workspaceEdit { grid-column: 2; grid-row: 1; padding: 0; border: 0; color: #1677ff; background: transparent; font: inherit; font-weight: 560; white-space: nowrap; cursor: pointer; }
163
164
  .dim-panel .dim-workspaceEdit:disabled { cursor: not-allowed; opacity: .55; }
164
- .dim-panel .dim-workspacePath { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; display: block; overflow-x: auto; overflow-y: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
165
+ .dim-panel .dim-workspacePath { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; display: block; overflow: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; white-space: normal; }
165
166
  .dim-panel .dim-preset { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; align-items: center; column-gap: 10px; row-gap: 4px; margin-top: 6px; padding: 6px 10px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
166
167
  .dim-panel .dim-presetHeader { position: relative; min-width: 0; grid-column: 1 / -1; grid-row: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; }
167
168
  .dim-panel .dim-presetTitle { min-width: 0; display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
@@ -216,7 +217,7 @@ const CSS = String.raw`
216
217
  .dim-directoryPickerActions .dim-directoryPickerPrimary { border-color: var(--dim-blue); color: #fff; background: var(--dim-blue); }
217
218
  .dim-directoryPickerActions button:hover:not(:disabled) { filter: brightness(.97); }
218
219
  .dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; }
219
- .dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; }
220
+ .dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; overflow-wrap: anywhere; white-space: normal; }
220
221
  .dim-panel .dim-cardFooterLayout { min-width: 0; width: 100%; display: flex; flex-direction: column; align-items: stretch; gap: 9px; }
221
222
  .dim-panel .dim-cardFooterLayout > .dim-cardActions { align-self: stretch; }
222
223
  .dim-panel .dim-cardFeedback { width: 100%; padding: 8px 10px; border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 12px; font-weight: 400; line-height: 18px; overflow-wrap: anywhere; white-space: normal; }
@@ -224,17 +225,17 @@ const CSS = String.raw`
224
225
  .dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
225
226
  .dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
226
227
  .dim-panel .dim-cardActions .dim-cardAction[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); }
227
- .dim-panel .dim-botCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
228
+ .dim-panel .dim-botCard { position: relative; min-width: 0; width: 100%; max-width: 100%; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
228
229
  .dim-panel .dim-botCard::before { display: none; }
229
- .dim-panel .dim-botCardBody { position: relative; padding: 12px; }
230
- .dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
231
- .dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap: 10px; }
230
+ .dim-panel .dim-botCardBody { position: relative; min-width: 0; width: 100%; max-width: 100%; padding: 12px; }
231
+ .dim-panel .dim-botCardTop { min-width: 0; max-width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
232
+ .dim-panel .dim-botIdentity { min-width: 0; flex: 1 1 0; display: flex; align-items: center; gap: 10px; }
232
233
  .dim-panel .dim-botAvatar { flex: none; width: 38px; height: 38px; display: grid; place-items: center; overflow: hidden; border-radius: 11px; box-shadow: none; }
233
234
  .dim-panel .dim-botAvatar svg { width: 27px; height: 27px; }
234
235
  .dim-panel .dim-botName { min-width: 0; }
235
236
  .dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
236
237
  .dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
237
- .dim-panel .dim-botHealthGroup { flex: none; display: grid; justify-items: end; gap: 5px; }
238
+ .dim-panel .dim-botHealthGroup { min-width: 0; max-width: 100%; flex: none; display: grid; justify-items: end; gap: 5px; }
238
239
  .dim-panel .dim-botCard .dim-botHealth { flex: none; min-height: 0; display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0; border-radius: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; }
239
240
  .dim-panel .dim-lastChecked { display: inline-flex; align-items: baseline; gap: 4px; color: var(--dsw-alias-label-tertiary, #8f959e); font: inherit; font-size: 11px; font-weight: 400; line-height: normal; white-space: nowrap; }
240
241
  .dim-panel .dim-botCard .dim-healthDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; box-shadow: none; }
@@ -246,6 +247,8 @@ const CSS = String.raw`
246
247
  .dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); }
247
248
  @container (max-width: 680px) {
248
249
  .dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { gap: 6px; }
250
+ .dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; }
251
+ .dim-panel .dim-botHealthGroup { justify-items: start; }
249
252
  .dim-panel .dim-bindActions { gap: 6px; }
250
253
  .dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton, .dim-panel .dim-credentialButton { gap: 5px; padding-inline: 8px; font-size: 12px; }
251
254
  .dim-panel .dim-actionIcon { width: 13px; height: 13px; flex-basis: 13px; }
@@ -1,8 +1,23 @@
1
- import { access, readFile, stat } from 'node:fs/promises';
1
+ import { access, readFile, readdir, stat } from 'node:fs/promises';
2
2
  import { resolve } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
 
5
5
  const root = resolve(import.meta.dirname, '..');
6
+
7
+ async function readSourceTree(directory) {
8
+ const entries = await readdir(directory, { withFileTypes: true });
9
+ const chunks = [];
10
+ for (const entry of entries) {
11
+ const path = resolve(directory, entry.name);
12
+ if (entry.isDirectory()) {
13
+ chunks.push(await readSourceTree(path));
14
+ } else if (entry.isFile() && /\.m?js$/u.test(entry.name)) {
15
+ chunks.push(await readFile(path, 'utf8'));
16
+ }
17
+ }
18
+ return chunks.join('\n');
19
+ }
20
+
6
21
  const required = [
7
22
  'lib/index.js',
8
23
  'lib/client.js',
@@ -35,7 +50,17 @@ const required = [
35
50
  ];
36
51
  await Promise.all(required.map((path) => access(resolve(root, path))));
37
52
 
38
- const [client, host, patch, manifestText, lockText, hostSource, clientSource, executable] = await Promise.all([
53
+ const [
54
+ client,
55
+ host,
56
+ patch,
57
+ manifestText,
58
+ lockText,
59
+ hostSource,
60
+ clientEntrySource,
61
+ clientSources,
62
+ executable,
63
+ ] = await Promise.all([
39
64
  readFile(resolve(root, 'lib/client.js'), 'utf8'),
40
65
  readFile(resolve(root, 'lib/index.js'), 'utf8'),
41
66
  readFile(resolve(root, 'cordis.patch.yml'), 'utf8'),
@@ -43,6 +68,7 @@ const [client, host, patch, manifestText, lockText, hostSource, clientSource, ex
43
68
  readFile(resolve(root, 'package-lock.json'), 'utf8'),
44
69
  readFile(resolve(root, 'plugin-src/host/index.mjs'), 'utf8'),
45
70
  readFile(resolve(root, 'plugin-src/client/index.js'), 'utf8'),
71
+ readSourceTree(resolve(root, 'plugin-src/client')),
46
72
  stat(resolve(root, 'bin/dsh-im.mjs')),
47
73
  ]);
48
74
  const manifest = JSON.parse(manifestText);
@@ -82,17 +108,28 @@ if (forbiddenDshLockPaths.length > 0) {
82
108
  );
83
109
  }
84
110
 
85
- if (!client.includes('id: "@xmanrui/dsh-im"')) {
111
+ if (!/\bid\s*:\s*["']@xmanrui\/dsh-im["']/u.test(client)) {
86
112
  throw new Error('client bundle does not register the dsh-im loader id');
87
113
  }
88
- if (!client.includes('id: "im"')
89
- || !client.includes('label: () => t("IM\\u673A\\u5668\\u4EBA")')
90
- || !client.includes('locale: IM_LOCALE_NAMESPACE')
91
- || !client.includes('IM_LOCALE_NAMESPACE = "dsh-im"')) {
92
- throw new Error('client bundle does not register the localized IM settings tab');
114
+ const sourceSectionMarkers = [
115
+ /ctx\.slots\.inject\(\s*["']settings\.section["']/u,
116
+ /name\s*:\s*["']settings\.section["']/u,
117
+ /id\s*:\s*["']xmanrui-dsh-im["']/u,
118
+ /order\s*:\s*21\b/u,
119
+ /label\s*:\s*\(\)\s*=>\s*t\(\s*["']IM机器人["']\s*\)/u,
120
+ /locale\s*:\s*IM_LOCALE_NAMESPACE\b/u,
121
+ ];
122
+ const bundleSectionPattern = /name\s*:\s*["']settings\.section["']\s*,\s*id\s*:\s*["']xmanrui-dsh-im["']\s*,\s*order\s*:\s*21\s*,\s*label\s*:\s*\(\)\s*=>\s*[$A-Z_a-z][$\w]*\(\s*["']IM(?:机器人|\\u673A\\u5668\\u4EBA)["']\s*\)\s*,\s*locale\s*:\s*(?:[$A-Z_a-z][$\w]*|["']dsh-im["'])/u;
123
+ if (sourceSectionMarkers.some((pattern) => !pattern.test(clientEntrySource))
124
+ || !/IM_LOCALE_NAMESPACE\s*=\s*["']dsh-im["']/u.test(clientSources)
125
+ || !bundleSectionPattern.test(client)) {
126
+ throw new Error('client bundle does not register the localized top-level IM settings section');
127
+ }
128
+ if ((client.match(/\.slots\.inject\(\s*["']settings\.section["']/gu) ?? []).length !== 1) {
129
+ throw new Error('client bundle must register exactly one top-level settings section');
93
130
  }
94
- if ((client.match(/ctx\.slots\.inject\("settings\.plugins\.tab"/g) ?? []).length !== 1) {
95
- throw new Error('client bundle must register exactly one settings tab');
131
+ if (client.includes('settings.plugins.tab') || clientSources.includes('settings.plugins.tab')) {
132
+ throw new Error('client source or bundle still contains the legacy Plugins-tab settings entry');
96
133
  }
97
134
  if (/role:\s*["']switch|type:\s*["']checkbox/.test(client)) {
98
135
  throw new Error('client bundle contains a channel enable switch');
@@ -115,7 +152,7 @@ if (/@xmanrui\/dsh-(?:feishu|weixin|dingtalk)/.test(host)) {
115
152
  throw new Error('host bundle still imports an external channel plugin');
116
153
  }
117
154
  if (/@xmanrui\/dsh-(?:feishu|weixin|dingtalk)/.test(
118
- manifestText + lockText + hostSource + clientSource,
155
+ manifestText + lockText + hostSource + clientSources,
119
156
  )) {
120
157
  throw new Error('source or package metadata still depends on an external channel plugin');
121
158
  }
@@ -159,7 +159,7 @@ function artifactFailureText(fileName, error) {
159
159
  const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim() || t('结果文件');
160
160
  switch (error?.code) {
161
161
  case 'artifact-permission-required':
162
- return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。', { name });
162
+ return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在「IM机器人」设置页点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。', { name });
163
163
  case 'artifact-too-large':
164
164
  return t('结果文件「{name}」超过飞书 30 MB 上限,未发送。', { name });
165
165
  case 'artifact-empty':
@@ -945,7 +945,10 @@ export class FeishuHarnessBridge {
945
945
  const text = message.content;
946
946
  const hasImages = hasInboundImages(message);
947
947
  const hasFiles = hasInboundFiles(message);
948
- const commandText = event.message.message_type === 'text' && !hasImages && !hasFiles ? text : null;
948
+ // 命令识别对 text 与纯文本 post 一视同仁:post 富文本若仅含单个
949
+ // 文本段落(如复制粘贴的 /new),同样按命令处理;带图片/文件不认。
950
+ // accept() 侧已用 nonEmptyString(content) 判定,两侧保持一致。
951
+ const commandText = !hasImages && !hasFiles && text ? text.trim() : null;
949
952
  if (!text && !hasImages && !hasFiles) {
950
953
  await this.#send(event.message.chat_id, t('目前支持文字、图片和文件消息。'));
951
954
  return;
@@ -5,7 +5,7 @@ const FEISHU_MISSING_MESSAGE_SCOPE_CODE = 99991672;
5
5
  const FEISHU_ERROR_BODY_LIMIT = 64 * 1024;
6
6
  const FEISHU_ERROR_BODY_TIMEOUT_MS = 1_000;
7
7
  const FEISHU_IMAGE_PERMISSION_MESSAGE =
8
- '飞书机器人缺少图片读取权限 im:message:readonly(飞书显示为“获取单聊、群组消息”)。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。按飞书提示发布新版本、完成必要审批后,再重新发送图片。';
8
+ '飞书机器人缺少图片读取权限 im:message:readonly(飞书显示为“获取单聊、群组消息”)。请私聊机器人执行 /repair 命令,或者在「IM机器人」设置页点击“补全权限”按钮并扫码。按飞书提示发布新版本、完成必要审批后,再重新发送图片。';
9
9
 
10
10
  export function conversationKey(event) {
11
11
  const chatType = event?.message?.chat_type;