@wenbin_wb/dsh-bridge 2.2.9 → 2.3.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/README.en.md CHANGED
@@ -19,6 +19,7 @@ Seamlessly extend your local DeepSeek Harness to mobile phones, tablets, public
19
19
  - **Custom Tunnel**: Connect to your own tunnel server with a fixed domain ([Setup Guide](docs/custom-tunnel.md))
20
20
  - **WeChat Bot (ClawBot / iLink)**: Scan a QR code to log in a WeChat personal account, then chat with, control, and approve your DeepSeek Harness agents right inside WeChat. **Multi-workspace selection, restart-persistent sessions, grouped session listing with titles, media (image/file/voice) transfer, and permission approvals** — over Tencent's official iLink Bot API, no public server or tunnel required ([Usage Guide](docs/wechat-usage.md))
21
21
  - **QQ Bot (OpenAPI v2)**: Connect your QQ Bot to receive private/group messages, send Markdown, button keyboards, and rich media. **Full event coverage (C2C / GROUP_AT_MESSAGE_CREATE), auto token refresh, reconnection with backoff, message deduplication** — over Tencent's official QQ Bot OpenAPI v2 ([Usage Guide](docs/qq-usage.md))
22
+ - **Feishu / Lark Bot (Official WebSocket)**: Connect enterprise self-built apps via Feishu's official WebSocket protocol. **No public IP / no Webhook required, Markdown table formatting, native interactive card permission approvals with 1-click button actions** ([Usage Guide](docs/feishu-usage.md))
22
23
  - **Official Brand SVG Icons**: Authentic vector brand icons for WeChat, QQ, Feishu, Telegram with real-time status indicators
23
24
  - **Fast Version Check & 1-Click Upgrade**: Dual-channel registry check (npmmirror + npmjs fallback in ~200ms) with seamless **1-click in-app upgrade**, no terminal copying required
24
25
  - **Dark Mode Support**: Deep integration with DeepSeek Harness Design Tokens (`--dsw-alias-*`), QR code background protection for safe dark mode scanning
@@ -38,7 +39,7 @@ Seamlessly extend your local DeepSeek Harness to mobile phones, tablets, public
38
39
  | **Platform Abstraction** | Platform-agnostic core (sessions / approvals / commands / digest) shared across IM channels | ✅ **Completed** (v2.0.0) |
39
40
  | **WeChat** | Chat with your Agent directly in WeChat | ✅ Supported (workspaces / persisted sessions / media / approvals) |
40
41
  | **QQ Bot** | QQ bot integration for group/private chat | ✅ **Completed** (v2.1.0) — Markdown / buttons / rich media |
41
- | **Feishu** | Feishu message/bot integration for workplace scenarios | Planned |
42
+ | **Feishu** | Feishu / Lark official persistent WebSocket bot | **Completed** (v2.3.0) — No-public-IP WS / Card Approvals |
42
43
  | **Telegram** | IM channel suited for self-hosting and overseas | Planned |
43
44
  | **OpenClaw** | Connect with the OpenClaw ecosystem | Planned |
44
45
 
@@ -245,6 +246,43 @@ Integrates Tencent's official QQ Bot platform. Supports private chat, group chat
245
246
 
246
247
  ---
247
248
 
249
+ ### Feishu / Lark Bot (Official WebSocket)
250
+
251
+ Connect your enterprise self-built app via Feishu's official WebSocket protocol. Supports private chat and group mentions without public IP, domain name, or webhook configuration.
252
+
253
+ **Key Highlights**
254
+
255
+ - ⚡ **100% No Public IP Required**: Direct duplex WebSocket connection to Feishu Open Platform
256
+ - 📜 **Card JSON 2.0 Streaming**: In-place single-card incremental streaming updates, eliminating message bubble fragmentation
257
+ - 🛡️ **Card 2.0 Interactive Approvals**: Native orange approval card with `[✓ Approve]` / `[✕ Reject]` action buttons for 1-click execution
258
+ - 📝 **Full Markdown Rendering**: Native support for headings, tables, syntax highlighting, blockquotes, and lists
259
+ - 🔄 **Workspace & Session Management**: Table-formatted `/sessions`, `/use N` switching, and `/workspaces` listing
260
+
261
+ **Setup Steps**
262
+
263
+ 1. Go to [Feishu Open Platform](https://open.feishu.cn/app) to create a self-built app, enable "Bot" capability, and publish a version ([Detailed Guide](docs/feishu-usage.md))
264
+ 2. Under "Events & Callbacks", select "Use WebSocket to receive events", and add `im.message.receive_v1` & `card.action.trigger`
265
+ 3. Open DSH Settings → "Remote Access" → "IM Bots" → select "Feishu"
266
+ 4. Fill in App ID and App Secret, then click "Save & Connect"
267
+
268
+ **Feishu Bot Commands** (Full guide in [Feishu Bot Usage Guide](docs/feishu-usage.md))
269
+
270
+ | Command | Description |
271
+ |---------|-------------|
272
+ | *(plain text)* | Send to current active agent |
273
+ | `/new <prompt>` | Create and start a new session in current workspace |
274
+ | `/new <prompt> @N` | Create a new session in workspace N |
275
+ | `/sessions` (or `/list`) | List all sessions in a structured Markdown table |
276
+ | `/use N` (or `/resume N`) | Switch to/resume session N |
277
+ | `/workspaces` | List all available workspaces |
278
+ | `/end` | End current session |
279
+ | `/stop` | Stop currently executing task |
280
+ | `/status` | View agent status dashboard |
281
+ | `/yes` `/no` (or `1`/`2`) | Respond to permission approval requests (or click card buttons) |
282
+ | `/help` | Display full command help |
283
+
284
+ ---
285
+
248
286
  ## Optional Configuration
249
287
 
250
288
  The plugin is ready to use out of the box. To customize the proxy port, add to `cordis.yml`:
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - **自建隧道**:连接自己的隧道服务器,获得固定域名([搭建教程](docs/custom-tunnel.md))
20
20
  - **微信 Bot(ClawBot / iLink)**:扫码登录微信个人号后,直接在微信里对话、控制 DeepSeek Harness 的 agent。**支持多工作区选择、会话跨重启持久化、按工作区分组查看、媒体(图片/文件/语音)收发、权限审批**——走腾讯官方 iLink Bot API,无需公网([使用说明](docs/wechat-usage.md))
21
21
  - **QQ Bot(OpenAPI v2)**:接入 QQ 机器人,私聊/群聊接收消息,发送 Markdown、按钮键盘和富媒体。**完整事件覆盖(C2C / GROUP_AT_MESSAGE_CREATE)、Token 自动刷新、断线重连、消息去重**——走腾讯官方 QQ Bot OpenAPI v2([使用说明](docs/qq-usage.md))
22
+ - **飞书 Bot(官方 WebSocket 长连接)**:接入飞书开放平台企业自建应用,私聊/群聊实时交互。**无需公网 IP / 无需 Webhook、支持飞书 Markdown 表格排版、原生交互卡片权限审批一键点击确认**——走飞书官方最新 WebSocket 长连接协议([使用说明](docs/feishu-usage.md))
22
23
  - **IM 官方品牌矢量图标**:微信、QQ、飞书、Telegram 官方矢量图标与状态展示,直接在聊天软件里呼唤你的 Agent
23
24
  - **极速版本检查与一键升级**:国内高速镜像(npmmirror)优先 + 官方源毫秒级双通道检查,检测到新版本支持**界面一键直接升级**,无需手动打开终端
24
25
  - **深色模式深度适配**:完美适配 DeepSeek Harness 设计系统明暗主题切换,二维码自带白底安全垫,暗光下手机扫码 100% 极速识别
@@ -38,7 +39,7 @@
38
39
  | **平台抽象层** | 平台无关的核心(会话/审批/命令/digest)跨 IM 渠道复用 | ✅ **已完成**(v2.0.0) |
39
40
  | **微信** | 在微信里直接与你的 Agent 对话 | ✅ 已支持(多工作区 / 会话持久化 / 媒体 / 审批) |
40
41
  | **QQ Bot** | 接入 QQ 机器人,群聊/私聊唤起 Agent | ✅ **已完成**(v2.1.0)— Markdown / 按钮 / 富媒体 |
41
- | **飞书** | 飞书消息/机器人集成,办公场景直接调用 | 规划中 |
42
+ | **飞书** | 飞书开放平台长连接机器人,办公场景直接调用 | **已完成**(v2.3.0)— 免公网 WS / 卡片审批 |
42
43
  | **Telegram** | 适合自托管与海外的 IM 渠道 | 规划中 |
43
44
  | **OpenClaw** | 与 OpenClaw 生态打通 | 规划中 |
44
45
 
@@ -245,6 +246,43 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
245
246
 
246
247
  ---
247
248
 
249
+ ### 飞书 Bot(官方 WebSocket 长连接)
250
+
251
+ 接入飞书开放平台企业自建应用,支持单聊与群聊(群聊需 @机器人)。走飞书官方最新 WebSocket 长连接协议,无需公网 IP、无需域名、免配置 Webhook。
252
+
253
+ **功能亮点**
254
+
255
+ - ⚡ **100% 免公网 IP**:官方 WebSocket 全双工长连接,本地电脑即可直连飞书开放平台
256
+ - 📜 **Card JSON 2.0 原生流式打字机**:单条卡片原地增量打字机更新,彻底告别气泡拆分碎片化
257
+ - 🛡️ **Card 2.0 交互卡片审批**:敏感操作触发审批时下发橙色告警卡片,手机/电脑端点击 `[✓ 批准执行]` / `[✕ 拒绝执行]` 按钮一键处理
258
+ - 📝 **全量 Markdown 渲染**:支持多级标题、表格、代码高亮、引用块与列表
259
+ - 🔄 **会话与工作区管理**:支持 `/sessions` 表格化查看、`/use N` 切换、`/workspaces` 查看工作区
260
+
261
+ **使用步骤**
262
+
263
+ 1. 前往 [飞书开放平台](https://open.feishu.cn/app) 创建企业自建应用,开启「机器人」能力并发布版本(详见 [飞书接入指南](docs/feishu-usage.md))
264
+ 2. 在「事件与回调」中开启「使用长连接接收事件」,添加 `im.message.receive_v1` 和 `card.action.trigger` 事件
265
+ 3. 打开 DSH 设置页「远程访问」→「IM 机器人」→ 选中「飞书」
266
+ 4. 填入 App ID 和 App Secret,点击「保存并连接」即可
267
+
268
+ **飞书里的命令**(完整说明见 [飞书 Bot 使用说明](docs/feishu-usage.md))
269
+
270
+ | 命令 | 说明 |
271
+ |------|------|
272
+ | *(普通文本)* | 发给当前活动 agent |
273
+ | `/new <提示词>` | 在当前工作区新建会话并开始 |
274
+ | `/new <提示词> @N` | 在指定工作区新建会话 |
275
+ | `/sessions`(或 `/list`) | 查看所有历史会话(结构化表格排版) |
276
+ | `/use N`(或 `/resume N`) | 切换到会话 N |
277
+ | `/workspaces` | 列出可用工作区 |
278
+ | `/end` | 结束当前会话 |
279
+ | `/stop` | 停止当前任务 |
280
+ | `/status` | 查看 agent 状态看板 |
281
+ | `/yes` `/no`(或 `1`/`2`) | 响应权限审批请求(或直接点击卡片按钮) |
282
+ | `/help` | 查看全部命令 |
283
+
284
+ ---
285
+
248
286
  ## 可选配置
249
287
 
250
288
  插件开箱即用,无需配置。如需修改代理端口,在 cordis.yml 中添加:
package/client/client.js CHANGED
@@ -425,7 +425,9 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
425
425
  sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
426
426
  appId: platform.config.appId ?? "",
427
427
  // Secret 不由后端回传;空值表示沿用已保存密钥
428
- clientSecret: ""
428
+ clientSecret: "",
429
+ appSecret: "",
430
+ domain: platform.config.domain ?? "feishu"
429
431
  });
430
432
  }
431
433
  }, [platform?.config]);
@@ -501,10 +503,14 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
501
503
  if (platformId === "qq") {
502
504
  payload.appId = cfgDraft.appId.trim();
503
505
  payload.clientSecret = cfgDraft.clientSecret.trim();
506
+ } else if (platformId === "feishu") {
507
+ payload.appId = cfgDraft.appId.trim();
508
+ payload.appSecret = cfgDraft.appSecret.trim();
509
+ payload.domain = cfgDraft.domain || "feishu";
504
510
  }
505
511
  await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
506
512
  }, [act, cfgDraft, platformId]);
507
- const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")));
513
+ const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")) || platformId === "feishu" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.appSecret !== (platform.config.appSecret ?? "")));
508
514
  if (!platform && !err) {
509
515
  return React.createElement(
510
516
  "div",
@@ -561,6 +567,18 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
561
567
  rel: "noopener noreferrer",
562
568
  style: s.btnGhost
563
569
  }, "\u{1F310} QQ \u5F00\u653E\u5E73\u53F0"),
570
+ platformId === "feishu" && React.createElement("a", {
571
+ href: "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/feishu-usage.md",
572
+ target: "_blank",
573
+ rel: "noopener noreferrer",
574
+ style: s.btnGhost
575
+ }, "\u{1F4D6} \u98DE\u4E66\u4F7F\u7528\u8BF4\u660E"),
576
+ platformId === "feishu" && React.createElement("a", {
577
+ href: "https://open.feishu.cn/app",
578
+ target: "_blank",
579
+ rel: "noopener noreferrer",
580
+ style: s.btnGhost
581
+ }, "\u{1F310} \u98DE\u4E66\u5F00\u653E\u5E73\u53F0"),
564
582
  React.createElement("button", {
565
583
  style: s.btnGhost,
566
584
  onClick: () => setShowHelp((v) => !v)
@@ -695,19 +713,57 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
695
713
  React.createElement(
696
714
  "div",
697
715
  { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
698
- platform.status !== "connected" && platform.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\u767B\u5F55"),
716
+ platform.status !== "connected" && platform.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\u8FDE\u63A5"),
699
717
  (platform.status === "connected" || platform.status === "starting") && React.createElement("button", { style: s.btnGhost, onClick: onStop, disabled: busy }, "\u65AD\u5F00"),
700
718
  React.createElement("button", {
701
719
  style: { ...s.btnGhost, color: "var(--dsw-alias-state-error-primary,#dc2626)", borderColor: "var(--dsw-alias-state-error-primary,#dc2626)", opacity: busy ? 0.5 : 1 },
702
720
  disabled: busy,
703
721
  onClick: () => {
704
- if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u767B\u5F55\u3002")) act(BRIDGE_ENDPOINTS.platformUnbind, {});
722
+ if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u4FDD\u5B58\u7684\u51ED\u8BC1\u3002")) act(BRIDGE_ENDPOINTS.platformUnbind, {});
705
723
  },
706
- title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u767B\u5F55"
724
+ title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u914D\u7F6E"
707
725
  }, "\u89E3\u7ED1\u8D26\u53F7")
726
+ ),
727
+ // 飞书专属:手机扫码直达 Bot 对话
728
+ platformId === "feishu" && platform.botQr && React.createElement(
729
+ "div",
730
+ {
731
+ style: {
732
+ marginTop: 12,
733
+ padding: 12,
734
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
735
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
736
+ borderRadius: 8,
737
+ display: "flex",
738
+ gap: 14,
739
+ alignItems: "center",
740
+ flexWrap: "wrap"
741
+ }
742
+ },
743
+ React.createElement("img", { src: platform.botQr, alt: "Feishu Bot QR", style: { width: 110, height: 110, borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", padding: 4, background: "#fff" } }),
744
+ React.createElement(
745
+ "div",
746
+ { style: { flex: 1, minWidth: 160 } },
747
+ React.createElement("div", { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, "\u{1F4F1} \u624B\u673A\u98DE\u4E66\u626B\u7801\u76F4\u8FBE\u5BF9\u8BDD"),
748
+ React.createElement(
749
+ "div",
750
+ { style: { ...s.muted, fontSize: 12, marginTop: 4, lineHeight: 1.5 } },
751
+ "\u7528\u98DE\u4E66\u624B\u673A App \u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801\uFF0C\u7ACB\u5373\u6253\u5F00\u4E0E Bot \u5BF9\u8BDD\uFF1B\u53D1\u9001\u9996\u6761\u6D88\u606F\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002"
752
+ ),
753
+ platform.botLink && React.createElement(
754
+ "div",
755
+ { style: { display: "flex", gap: 8, marginTop: 8 } },
756
+ React.createElement("a", {
757
+ href: platform.botLink,
758
+ target: "_blank",
759
+ rel: "noopener noreferrer",
760
+ style: { ...s.btnGhost, textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 10px", fontSize: 12 }
761
+ }, "\u5728\u98DE\u4E66\u5BA2\u6237\u7AEF\u6253\u5F00 \u2197")
762
+ )
763
+ )
708
764
  )
709
765
  ),
710
- // 未配置 / 登录中:二维码(QQ 无二维码,显示凭证表单)
766
+ // 未配置 / 登录中:表单(QQ / 飞书)或二维码(微信)
711
767
  (!platform?.configured || showQr) && React.createElement(
712
768
  "div",
713
769
  { style: s.block },
@@ -721,39 +777,58 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
721
777
  login.phase === "scaned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u2026" : platformId === "wechat" ? "\u8BF7\u4F7F\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF08ClawBot\uFF09" : "\u8BF7\u626B\u7801\u767B\u5F55"
722
778
  ),
723
779
  login.error && React.createElement("div", { style: { ...s.muted, marginTop: 4, color: "var(--dsw-alias-state-warn-primary,#92400e)" } }, login.error)
724
- ) : platformId === "qq" ? React.createElement(
780
+ ) : platformId === "qq" || platformId === "feishu" ? React.createElement(
725
781
  "div",
726
782
  { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4 } },
783
+ platformId === "feishu" && React.createElement(
784
+ "div",
785
+ {
786
+ style: {
787
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
788
+ border: "1px dashed var(--dsw-alias-border-l2,#e5e7eb)",
789
+ borderRadius: 6,
790
+ padding: "8px 10px",
791
+ fontSize: 12,
792
+ lineHeight: 1.5
793
+ }
794
+ },
795
+ React.createElement("span", { style: s.label }, "\u{1F4A1} \u626B\u7801\u81EA\u52A8\u521B\u5EFA\u5F15\u5BFC\uFF1A"),
796
+ React.createElement("span", { style: s.muted }, " \u53EF\u5728\u7EC8\u7AEF\u8FD0\u884C "),
797
+ React.createElement("code", { style: { ...s.code, fontSize: 11, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", padding: "2px 4px", borderRadius: 4 } }, "npx feishu-bot-bootstrap"),
798
+ React.createElement("span", { style: s.muted }, " \u624B\u673A\u626B\u7801\u4E00\u952E\u81EA\u52A8\u521B\u5EFA\u5E94\u7528\u5E76\u8F93\u51FA\u51ED\u8BC1\uFF1B\u6216\u5728\u4E0B\u65B9\u624B\u52A8\u586B\u5165\u51ED\u8BC1\u3002")
799
+ ),
727
800
  React.createElement(
728
801
  "div",
729
802
  null,
730
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID"),
803
+ React.createElement(
804
+ "div",
805
+ { style: { ...s.muted, marginBottom: 4 } },
806
+ platformId === "qq" ? "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID" : "App ID \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u81EA\u5EFA\u5E94\u7528 ID (cli_xxx)"
807
+ ),
731
808
  React.createElement("input", {
732
809
  style: { ...s.input, width: "100%" },
733
- placeholder: "\u8BF7\u8F93\u5165 AppID",
810
+ placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 AppID" : "\u8BF7\u8F93\u5165 App ID (\u5982 cli_a1b2c3d4...)",
734
811
  value: cfgDraft?.appId ?? "",
735
- onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value })),
736
- onKeyDown: (e) => {
737
- if (e.key === "Enter" && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig();
738
- }
812
+ onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
739
813
  })
740
814
  ),
741
815
  React.createElement(
742
816
  "div",
743
817
  null,
744
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5"),
818
+ React.createElement(
819
+ "div",
820
+ { style: { ...s.muted, marginBottom: 4 } },
821
+ platformId === "qq" ? "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5" : "App Secret \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u5E94\u7528\u5BC6\u94A5"
822
+ ),
745
823
  React.createElement(
746
824
  "div",
747
825
  { style: { display: "flex", gap: 6, alignItems: "center" } },
748
826
  React.createElement("input", {
749
827
  style: { ...s.input, flex: 1 },
750
828
  type: showSecret ? "text" : "password",
751
- placeholder: "\u8BF7\u8F93\u5165 ClientSecret",
752
- value: cfgDraft?.clientSecret ?? "",
753
- onChange: (e) => setCfgDraft((d) => ({ ...d, clientSecret: e.target.value })),
754
- onKeyDown: (e) => {
755
- if (e.key === "Enter" && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig();
756
- }
829
+ placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 ClientSecret" : "\u8BF7\u8F93\u5165 App Secret",
830
+ value: platformId === "qq" ? cfgDraft?.clientSecret ?? "" : cfgDraft?.appSecret ?? "",
831
+ onChange: (e) => setCfgDraft((d) => platformId === "qq" ? { ...d, clientSecret: e.target.value } : { ...d, appSecret: e.target.value })
757
832
  }),
758
833
  React.createElement("button", {
759
834
  style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
@@ -767,11 +842,11 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
767
842
  "div",
768
843
  null,
769
844
  React.createElement("a", {
770
- href: "https://bot.q.qq.com/wiki/develop/api-v2/",
845
+ href: platformId === "qq" ? "https://bot.q.qq.com/wiki/develop/api-v2/" : "https://open.feishu.cn/app",
771
846
  target: "_blank",
772
847
  rel: "noopener noreferrer",
773
848
  style: s.btnLink
774
- }, "\u{1F4D6} \u524D\u5F80 QQ \u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u673A\u5668\u4EBA")
849
+ }, platformId === "qq" ? "\u{1F4D6} \u524D\u5F80 QQ \u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u673A\u5668\u4EBA" : "\u{1F4D6} \u524D\u5F80\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u521B\u5EFA\u4F01\u4E1A\u81EA\u5EFA\u5E94\u7528")
775
850
  ),
776
851
  React.createElement(
777
852
  "div",
@@ -779,7 +854,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
779
854
  React.createElement("button", {
780
855
  style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
781
856
  onClick: saveConfig,
782
- disabled: busy || !cfgDraft?.appId?.trim() || !cfgDraft?.clientSecret?.trim()
857
+ disabled: busy || !cfgDraft?.appId?.trim() || (platformId === "qq" ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim())
783
858
  }, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5E76\u8FDE\u63A5"),
784
859
  login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u8FDE\u63A5\u5931\u8D25")
785
860
  )
@@ -794,138 +869,6 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
794
869
  login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u767B\u5F55\u5931\u8D25")
795
870
  )
796
871
  ),
797
- // 高级设置(紧凑 2x2 网格 + 恢复默认值)
798
- cfgDraft && React.createElement(
799
- "div",
800
- { style: s.block },
801
- React.createElement("button", {
802
- style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
803
- onClick: () => setShowAdvanced((v) => !v)
804
- }, showAdvanced ? "\u25BE \u9AD8\u7EA7\u8BBE\u7F6E" : "\u25B8 \u9AD8\u7EA7\u8BBE\u7F6E"),
805
- showAdvanced && React.createElement(
806
- "div",
807
- { style: { display: "flex", flexDirection: "column", gap: 10 } },
808
- // 2x2 参数网格
809
- React.createElement(
810
- "div",
811
- {
812
- style: {
813
- display: "grid",
814
- gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
815
- gap: 10
816
- }
817
- },
818
- // 心跳间隔
819
- React.createElement(
820
- "div",
821
- null,
822
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5FC3\u8DF3\u8FDB\u5EA6\u95F4\u9694 (\u79D2)"),
823
- React.createElement("input", {
824
- style: { ...s.input, width: "100%" },
825
- type: "number",
826
- min: 30,
827
- max: 3600,
828
- value: cfgDraft.digestIntervalSec,
829
- onChange: (e) => setCfgDraft((d) => ({ ...d, digestIntervalSec: e.target.value }))
830
- })
831
- ),
832
- // 审批超时
833
- React.createElement(
834
- "div",
835
- null,
836
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5BA1\u6279\u8D85\u65F6\u81EA\u52A8\u62D2\u7EDD (\u79D2)"),
837
- React.createElement("input", {
838
- style: { ...s.input, width: "100%" },
839
- type: "number",
840
- min: 30,
841
- max: 86400,
842
- value: cfgDraft.approvalTimeoutSec,
843
- onChange: (e) => setCfgDraft((d) => ({ ...d, approvalTimeoutSec: e.target.value }))
844
- })
845
- ),
846
- // 每气泡字数
847
- React.createElement(
848
- "div",
849
- null,
850
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5355\u6761\u6700\u5927\u5B57\u6570\u9650\u5236 (\u5B57)"),
851
- React.createElement("input", {
852
- style: { ...s.input, width: "100%" },
853
- type: "number",
854
- min: 100,
855
- max: 1e4,
856
- value: cfgDraft.maxMessageChars,
857
- onChange: (e) => setCfgDraft((d) => ({ ...d, maxMessageChars: e.target.value }))
858
- })
859
- ),
860
- // 分块延迟
861
- React.createElement(
862
- "div",
863
- null,
864
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5206\u5757\u53D1\u9001\u5EF6\u8FDF (\u6BEB\u79D2)"),
865
- React.createElement("input", {
866
- style: { ...s.input, width: "100%" },
867
- type: "number",
868
- min: 0,
869
- max: 1e4,
870
- value: cfgDraft.sendChunkDelayMs,
871
- onChange: (e) => setCfgDraft((d) => ({ ...d, sendChunkDelayMs: e.target.value }))
872
- })
873
- )
874
- ),
875
- // QQ 平台凭证
876
- platformId === "qq" && React.createElement(
877
- "div",
878
- { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4 } },
879
- React.createElement(
880
- "div",
881
- null,
882
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID"),
883
- React.createElement("input", {
884
- style: { ...s.input, width: "100%" },
885
- placeholder: "\u8BF7\u8F93\u5165 AppID",
886
- value: cfgDraft.appId,
887
- onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
888
- })
889
- ),
890
- React.createElement(
891
- "div",
892
- null,
893
- React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5\uFF08\u7559\u7A7A\u5219\u4FDD\u6301\u5DF2\u4FDD\u5B58\u5BC6\u94A5\uFF09"),
894
- React.createElement(
895
- "div",
896
- { style: { display: "flex", gap: 6, alignItems: "center" } },
897
- React.createElement("input", {
898
- style: { ...s.input, flex: 1 },
899
- type: showSecret ? "text" : "password",
900
- placeholder: "\u7559\u7A7A\u4FDD\u6301\u5DF2\u4FDD\u5B58\u5BC6\u94A5",
901
- value: cfgDraft.clientSecret,
902
- onChange: (e) => setCfgDraft((d) => ({ ...d, clientSecret: e.target.value }))
903
- }),
904
- React.createElement("button", {
905
- style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
906
- onClick: () => setShowSecret((v) => !v),
907
- type: "button",
908
- title: showSecret ? "\u9690\u85CF\u5BC6\u94A5" : "\u663E\u793A\u660E\u6587"
909
- }, showSecret ? "\u{1F648} \u9690\u85CF" : "\u{1F441}\uFE0F \u663E\u793A")
910
- )
911
- )
912
- ),
913
- React.createElement(
914
- "div",
915
- { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 4 } },
916
- React.createElement("button", {
917
- style: { ...s.btnPri, opacity: cfgDirty && !busy ? 1 : 0.5 },
918
- disabled: !cfgDirty || busy,
919
- onClick: saveConfig
920
- }, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u8BBE\u7F6E"),
921
- React.createElement("button", {
922
- style: { ...s.btnGhost, height: 32, fontSize: 12 },
923
- onClick: resetDefaults,
924
- title: "\u6062\u590D\u63A8\u8350\u9ED8\u8BA4\u914D\u7F6E"
925
- }, "\u21BA \u6062\u590D\u63A8\u8350\u9ED8\u8BA4")
926
- )
927
- )
928
- ),
929
872
  React.createElement(
930
873
  "div",
931
874
  { style: s.block },
@@ -1365,7 +1308,7 @@ function BridgePanel({ rpcCall }) {
1365
1308
  const IM_PLATFORMS = [
1366
1309
  { id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
1367
1310
  { id: "qq", label: "QQ", icon: Icons.qq, brandColor: "#12B7F5", desc: "\u5B98\u65B9\u673A\u5668\u4EBA \xB7 \u79C1\u804A/\u7FA4\u804A/\u6309\u94AE" },
1368
- { id: "feishu", label: "\u98DE\u4E66", icon: Icons.feishu, brandColor: "#00D6B9", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API" },
1311
+ { id: "feishu", label: "\u98DE\u4E66", icon: Icons.feishu, brandColor: "#00D6B9", desc: "\u5B98\u65B9 WebSocket \u957F\u8FDE\u63A5 \xB7 \u514D\u516C\u7F51" },
1369
1312
  { id: "telegram", label: "Telegram", icon: Icons.telegram, brandColor: "#24A1DE", desc: "\u5B98\u65B9 Bot API" }
1370
1313
  ];
1371
1314
  tabContent = React.createElement(