@wenbin_wb/dsh-bridge 2.10.9 → 2.10.11
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/CHANGELOG.md +27 -0
- package/client/client.js +251 -11
- package/client/index.js +146 -8
- package/client/mobile-styles.js +18 -2
- package/client/resource-url-compat.js +90 -0
- package/docs/telegram-usage.md +1 -1
- package/lib/bridge-rpc-constants.js +2 -0
- package/lib/bridge-rpc.js +23 -0
- package/lib/compat.js +172 -129
- package/lib/feishu/index.js +234 -225
- package/lib/feishu/node.js +439 -439
- package/lib/index.js +157 -41
- package/lib/platform/base.js +147 -147
- package/lib/platform/commands.js +235 -221
- package/lib/platform/config-restore.js +69 -0
- package/lib/platform/conversation-bridge.js +905 -821
- package/lib/platform/dsh-storage.js +117 -117
- package/lib/platform/index.js +10 -10
- package/lib/platform/message-split.js +229 -229
- package/lib/platform/session-catalog.js +394 -372
- package/lib/platform/session-config.js +52 -0
- package/lib/platform/stream-slices.js +21 -21
- package/lib/qq/index.js +322 -312
- package/lib/restart-helper.mjs +101 -0
- package/lib/telegram/index.js +225 -216
- package/lib/telegram/node.js +322 -322
- package/lib/wechat/gateway.js +986 -986
- package/lib/wechat/index.js +254 -244
- package/lib/wechat/media.js +285 -285
- package/lib/wechat/node.js +352 -352
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [v2.10.11] - 2026-09-16
|
|
8
|
+
|
|
9
|
+
### 🐞 修复
|
|
10
|
+
|
|
11
|
+
- **移动端顶栏压住 `position: fixed` 全屏面板顶部 52px**(issue #41):桥的让位方式此前是给内容框架加 `padding-top: var(--dsh-mobile-header-h)`,但 `position: fixed` 元素的包含块是视口(CSS 2.1 §10.1),不跟随祖先内边距。官方右侧栏在窄视口(`viewportWidth < 768`)自动全屏(`.P3OORG_panel[data-sidebar-right-panel=fullscreen]`,即 `fixed; inset:0; z-index:40`),其顶部标签栏因此落进顶栏(`z-index: 9998`)覆盖区。由于顶栏背景透明且容器自身 `pointer-events: none`,实际表现并非「被不透明条挡住」,而是顶栏标题/图标与面板标签压字,外加两处 40×40 命中区抢占点击——面板右端的「全屏 / 收起」按钮右半(14×28px)会被顶栏「+」按钮夺走,误触即触发新建会话。现按 `data-sidebar-right-panel="fullscreen"` 直接位移面板容器本身(`top` 下移 52px 并同步收窄 `height` / `max-height`),不依赖宿主 CSS-module 哈希类名,宿主升级不失效。若用户此前用 `top` 自行让位过,本次修复与之同属性、级联只生效一条,**不会叠加**;但若用的是 `padding-top` / `margin-top` / `transform` 位移,请撤掉旧规则,否则会叠加下移。
|
|
12
|
+
- **移动端断点与宿主判据对齐**:桥原按 `max-width: 768px`(运行时亦为 `innerWidth <= 768`),而宿主用 `viewportWidth < 768` 决定右侧栏是否自动全屏,恰在 768px 这一宽度上出现「桥渲染移动端顶栏、宿主按桌面(push)布局」的错位。现移动端统一为 `<= 767px`(新增 `MOBILE_MAX_WIDTH = 767` 常量,消除散落的魔法值),`768px` 起彻底交还桌面布局。
|
|
13
|
+
- **鸿蒙(HuaweiBrowser / ArkWeb)上文档预览永远显示「文件资源服务不可用。」**:该内核解析非特殊 scheme 时 `new URL('dsh-resource://file/session/<id>/<path>').hostname` 返回空串(Chrome/Safari 返回 `file`),而 DSH 的客户端资源注册表 `@deepseek-ai/dsh-client-resources` 正是拿 URL 的 hostname 当「协议键」——取不到就把记录判成「没有 provider」,状态永久停在 `none`;注册表只在 provider 注册那一刻回挂一次已有记录,之后不再重试,所以刷新也不恢复。表现为右侧栏文件与对话生成文档的预览全部打不开(而日志/探针里 `resources.providers` 其实一直有 `file`,容易误判成插件没生效)。现桥侧新增 `client/resource-url-compat.js`:**先做特性探测**,只有引擎确有差异时才包一层 `window.URL`,且仅对 `dsh-resource:` 协议、原生 hostname 为空、原始串能解析出 host 的实例补上 `hostname`/`host`(写成实例自有访问器,读取时原生值优先、为空才回退现解析,赋值/改 href 后立即反映新值;setter 仍委托原生原型);引擎正常时一个字节都不动(判定 `not-needed`)。根因在宿主侧(用 URL.hostname 当协议键),已另行整理上游材料;上游若改为从原始串解析协议,本兼容层自动空转。
|
|
14
|
+
- **iOS 16 / 旧 Safari 完全打不开 DSH(页面红屏 `Failed to load plugins … Can't find variable: Iterator`)**:DSH 自带的 pdf.js 在**模块顶层**就执行 `if (typeof Iterator.prototype.join !== 'function') Iterator.prototype.join = …`,而 `Iterator` 这个全局是 Safari 18.4 才随「迭代器辅助方法」引入的——iOS 16 上该引用直接抛 `Can't find variable: Iterator`,导致 `@deepseek-ai/dsh-client-ui-sidebar-documentpreview` 导入失败,进而整棵客户端插件树加载失败(实测于 iOS 16 真机,并在本机以同构环境复现)。同一批缺口还有 `Promise.withResolvers`(Safari 17.4+):DSH 宿主 HTML 末尾的启动握手内联脚本、以及审批 / 用户提问 / cordis 运行时 `ctx.timeout`·`ctx.interval` / workspace-files 变更订阅等运行期路径都在用它。现桥在反向代理注入 HTML 时、于宿主所有脚本之前补齐两枚垫片:`Promise.withResolvers`(完整语义)与 `Iterator`(把真实的内置迭代器共享原型 `%IteratorPrototype%` 挂到 `Iterator.prototype`,使 pdf.js 的补丁落到真实迭代器原型而不是一个没用的空对象)。两者都**只在缺失时安装**,iOS 18.4+ / Android / 桌面端零改动;至此 iOS 16 可正常启动并使用。
|
|
15
|
+
- **「重启 DSH」按钮在 systemd 托管下点了没用,DSH 再也没起来(只能手动重启),且不留任何日志**:桥原实现是不识别托管器的「自派生子进程 + `process.exit(0)`」。在 systemd 单元(如 `dsh-web.service`,用户在 user slice 下)里这必然失败:`Restart=on-failure` 不认干净退出的 code 0(systemd 不会拉起),而默认 `KillMode=control-group` 会在主进程退出时把刚派生的子进程一并杀掉;旧实现还把子进程 stdio 设成 `ignore`,所以连失败原因都看不到。现按托管方式分流:从 `/proc/self/cgroup` 取出**最内层** systemd 单元名 → 交给 `systemctl [--user] restart --no-block <unit>`(stop+start 由 systemd 负责,新进程仍在正确 cgroup 内,并尊重单元自身的 Restart 策略);`DSH_DAEMON` / `PM2_HOME` 仍走守护进程退出;无托管器时派生独立助手 `lib/restart-helper.mjs`,**等旧进程真正退出、端口释放后**才拉起新 DSH,并在最长 120s 内确认端口可连接,全过程写入 `~/.dsh/dsh-bridge/restart.log`(子进程输出另存 `restart-child.log`)。前端在宿主明确返回 `ok:false`(例如 `systemctl` 与助手都派生失败)时**直接报错**,不再假装「正在重连」让用户面对永远转圈。注意:systemd 托管的环境需先手动重启一次让本修复生效,之后按钮即可正常使用。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [v2.10.10] - 2026-09-14
|
|
20
|
+
|
|
21
|
+
### ✨ 新功能
|
|
22
|
+
|
|
23
|
+
- **设置页新增「高级设置」**(每个 IM 平台卡片内):可直接配置该平台远程会话的 **Agent 预设**(下拉列出本机可用预设,留空使用 DSH 默认预设)、**工作区目录**、**模型提供方 / 模型**,以及进度摘要间隔、审批超时、单条消息上限、分块发送间隔。保存后立即生效,并随宿主重启保留——此前这四个会话级配置没有任何界面入口,只能手改 `config.json`,节奏参数的面板也已在早前重构中丢失。
|
|
24
|
+
|
|
25
|
+
### 🐞 修复
|
|
26
|
+
|
|
27
|
+
- **DSH 0.1.5 系列上远程 IM 会话工具集残缺**(issue #40):DSH 从 `0.1.5` 起把 agent 侧的工具、提示词与技能目录整体挪到了 agent preset 后面(宿主层不再挂载 `tool-fs` / `tool-bash` / `tool-jobs` / `tool-skill` 等),改由每个会话在创建时挂载预设提供。桥此前只把预设名写进会话元数据、从未真正挂载,会话因此落在「空预设层」——表现为远程 Agent 缺少工具(例如没有读文件的工具)。现在新建与恢复会话都会真实挂载预设:未配置时使用 DSH 自身的默认预设;配置的预设在本机已不存在时回退默认预设,并在会话创建提示里明确告知,不再静默降级。
|
|
28
|
+
- **宿主重启后给已有会话发消息报 `session "..." already exists`、随后卡在「当前没有活动会话」**(issue #39):判断会话是否已持久化时沿用了旧的数据结构假设,而 DSH 0.1.5 的会话列表返回的是 `{ header, revision, sizeBytes }` 快照,`entry.id` 恒为空——已存在的历史会话被误判成新会话,走 `agents.create` 建同名会话而失败。现按真实结构取会话头,重启后能正常恢复续聊。
|
|
29
|
+
- **远程会话的工作区与预设配置重启即丢**(issue #40):`cwd`、`agentPreset`、`agentProvider`、`agentModel` 写进配置后,宿主重启即失效(恢复链路只回写了白名单里的少数字段),远程会话会掉到默认目录与默认预设。现已在重启恢复与配置备份导入时一并读回;同时补上 QQ / 飞书此前遗漏的摘要间隔、审批超时、分块延时恢复。配置的工作区目录不可用(被删、换机器、写成相对路径)时回落到已注册工作区并提示,不再把会话建在不存在的目录上。
|
|
30
|
+
- **`/rename` 改名重启即失效**:此前调用的是 `sessionPersistence.update()`——DSH 的持久化服务根本没有这个方法,被可选链静默跳过,改名只改了内存对象。现改用 DSH 原生会话标题服务(追加 `session/title` 事件并落盘),重启与 Web 侧边栏都能看到新标题;会话尚未恢复时会明确提示而不是假报成功。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
7
34
|
## [v2.10.9] - 2026-09-12
|
|
8
35
|
|
|
9
36
|
### 🐞 修复
|
package/client/client.js
CHANGED
|
@@ -51,7 +51,10 @@ var MOBILE_STYLES_CSS = `
|
|
|
51
51
|
--dsh-mobile-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/* \u65AD\u70B9\u4E0E\u5BBF\u4E3B\u5224\u636E\u5BF9\u9F50\uFF1A\u5BBF\u4E3B\u7528 viewportWidth < 768 \u51B3\u5B9A\u53F3\u4FA7\u680F\u81EA\u52A8\u5168\u5C4F
|
|
55
|
+
\uFF08dsh-client-ui-sidebar-right/lib/client.js\uFF09\uFF0C\u6545\u79FB\u52A8\u7AEF\u6837\u5F0F\u53D6 <=767px\uFF0C
|
|
56
|
+
768px \u8D77\u5F7B\u5E95\u4EA4\u8FD8\u684C\u9762\u5E03\u5C40\uFF0C\u907F\u514D"\u6865\u6E32\u67D3\u9876\u680F\u3001\u5BBF\u4E3B\u5374\u672A\u5168\u5C4F"\u7684\u9519\u4F4D\u3002 */
|
|
57
|
+
@media (max-width: 767px) {
|
|
55
58
|
/* 1. \u4E3B\u6846\u67B6\u4E3A Header \u817E\u51FA\u9876\u90E8\u7A7A\u95F4 */
|
|
56
59
|
div[class*="_frame"] {
|
|
57
60
|
display: flex !important;
|
|
@@ -66,6 +69,19 @@ var MOBILE_STYLES_CSS = `
|
|
|
66
69
|
box-sizing: border-box !important;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
/* 1.1 fixed \u5168\u5C4F\u9762\u677F\u5355\u72EC\u8BA9\u4F4D\uFF1Aposition:fixed \u7684\u5305\u542B\u5757\u662F viewport\uFF08CSS 2.1 \xA710.1\uFF09\uFF0C
|
|
73
|
+
\u4E0D\u8DDF\u968F\u4E0A\u9762 frame \u7684 padding-top\uFF0C\u56E0\u6B64\u9876\u90E8 52px \u4F1A\u843D\u8FDB\u9876\u680F\u8986\u76D6\u533A\uFF08#41\uFF09\u3002
|
|
74
|
+
\u5B98\u65B9\u53F3\u4FA7\u680F\u5728 <768px \u81EA\u52A8\u5168\u5C4F\uFF08fixed; inset:0; z-index:40\uFF09\uFF0C\u8FD9\u91CC\u6309 data \u5C5E\u6027
|
|
75
|
+
\u76F4\u63A5\u4F4D\u79FB\u5BB9\u5668\u672C\u8EAB\uFF0C\u4E0D\u4F9D\u8D56\u5BBF\u4E3B CSS-module \u54C8\u5E0C\u7C7B\u540D\u3002
|
|
76
|
+
\u771F\u6B63\u8D77\u4F5C\u7528\u7684\u662F top\uFF1Bheight/max-height \u4E0E\u4E0B\u9762\u5DE5\u4F5C\u53F0\u9762\u677F\u90A3\u6BB5\u4FDD\u6301\u540C\u4E00\u5199\u6CD5\uFF1A
|
|
77
|
+
\u5BBF\u4E3B\u5F53\u524D\u7528 inset:0\uFF08\u65E0\u663E\u5F0F\u9AD8\u5EA6\uFF09\u65F6 top \u5355\u72EC\u5373\u53EF\uFF0C\u4F46\u5BBF\u4E3B\u5C06\u6765\u82E5\u7ED9\u51FA\u663E\u5F0F\u9AD8\u5EA6\uFF0C
|
|
78
|
+
\u663E\u5F0F height \u4ECD\u80FD\u628A\u76D2\u5B50\u6536\u5728\u9876\u680F\u4E4B\u4E0B\u3002 */
|
|
79
|
+
[data-sidebar-right-panel="fullscreen"] {
|
|
80
|
+
top: var(--dsh-mobile-header-h, 52px) !important;
|
|
81
|
+
height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
82
|
+
max-height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
/* 2. \u9876\u90E8\u539F\u751F\u5BFC\u822A\u6761\uFF1A100% \u8FD8\u539F DeepSeek App (\u5DE6\u4FA7\u53CC\u6A2A\u7EBF\uFF0C\u53F3\u4FA7(+)\uFF0C\u4E2D\u95F4\u7559\u767D\uFF0C\u65E0\u591A\u4F59\u8BBE\u7F6E\u6309\u94AE) */
|
|
70
86
|
.dsh-mobile-app-header {
|
|
71
87
|
position: fixed !important;
|
|
@@ -923,7 +939,7 @@ var MOBILE_STYLES_CSS = `
|
|
|
923
939
|
--dsw-alias-label-primary-foreground: #0f1115;
|
|
924
940
|
}
|
|
925
941
|
|
|
926
|
-
@media (min-width:
|
|
942
|
+
@media (min-width: 768px) {
|
|
927
943
|
.dsh-mobile-app-header,
|
|
928
944
|
.dsh-mobile-backdrop,
|
|
929
945
|
.dsh-mobile-panel-close-btn {
|
|
@@ -1091,6 +1107,8 @@ var BRIDGE_ENDPOINTS = {
|
|
|
1091
1107
|
platformStop: "platformStop",
|
|
1092
1108
|
platformStart: "platformStart",
|
|
1093
1109
|
platformUnbind: "platformUnbind",
|
|
1110
|
+
// 本机可用的 DSH agent preset(设置页下拉用;老版本 DSH 返回 available:false)
|
|
1111
|
+
listAgentPresets: "listAgentPresets",
|
|
1094
1112
|
// 微信 Bot(v1.x 向后兼容别名,deprecated)
|
|
1095
1113
|
wechatGetStatus: "wechatGetStatus",
|
|
1096
1114
|
wechatLogin: "wechatLogin",
|
|
@@ -1101,6 +1119,81 @@ var BRIDGE_ENDPOINTS = {
|
|
|
1101
1119
|
wechatUnbind: "wechatUnbind"
|
|
1102
1120
|
};
|
|
1103
1121
|
|
|
1122
|
+
// client/resource-url-compat.js
|
|
1123
|
+
function installResourceUrlCompat(win = typeof window === "undefined" ? void 0 : window) {
|
|
1124
|
+
if (!win || typeof win.URL !== "function") return "no-url";
|
|
1125
|
+
const NativeURL = win.URL;
|
|
1126
|
+
let probe;
|
|
1127
|
+
try {
|
|
1128
|
+
probe = new NativeURL("dsh-resource://file/__dsh_probe__/x");
|
|
1129
|
+
} catch {
|
|
1130
|
+
return "probe-throw";
|
|
1131
|
+
}
|
|
1132
|
+
if (probe.hostname === "file") return "not-needed";
|
|
1133
|
+
const hostOf = (raw) => {
|
|
1134
|
+
const m = /^dsh-resource:\/\/([^/?#]*)/i.exec(String(raw || ""));
|
|
1135
|
+
return m && m[1] ? m[1].toLowerCase() : "";
|
|
1136
|
+
};
|
|
1137
|
+
function CompatURL(...args) {
|
|
1138
|
+
const url = new NativeURL(...args);
|
|
1139
|
+
try {
|
|
1140
|
+
if (String(url.protocol) === "dsh-resource:" && !url.hostname) {
|
|
1141
|
+
const proto = NativeURL.prototype;
|
|
1142
|
+
const hostnameDesc = Object.getOwnPropertyDescriptor(proto, "hostname");
|
|
1143
|
+
const hostDesc = Object.getOwnPropertyDescriptor(proto, "host");
|
|
1144
|
+
const hostOfUrl = (u) => {
|
|
1145
|
+
let native;
|
|
1146
|
+
try {
|
|
1147
|
+
native = hostnameDesc && hostnameDesc.get ? hostnameDesc.get.call(u) : u.hostname || "";
|
|
1148
|
+
} catch {
|
|
1149
|
+
native = "";
|
|
1150
|
+
}
|
|
1151
|
+
if (native) return native;
|
|
1152
|
+
let href;
|
|
1153
|
+
try {
|
|
1154
|
+
href = String(u.href);
|
|
1155
|
+
} catch {
|
|
1156
|
+
href = "";
|
|
1157
|
+
}
|
|
1158
|
+
return hostOf(href);
|
|
1159
|
+
};
|
|
1160
|
+
if (hostOfUrl(url)) {
|
|
1161
|
+
Object.defineProperty(url, "hostname", {
|
|
1162
|
+
configurable: true,
|
|
1163
|
+
enumerable: hostnameDesc ? hostnameDesc.enumerable : true,
|
|
1164
|
+
get: () => hostOfUrl(url),
|
|
1165
|
+
set: (v) => {
|
|
1166
|
+
if (hostnameDesc && hostnameDesc.set) hostnameDesc.set.call(url, v);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
if (hostDesc) {
|
|
1170
|
+
Object.defineProperty(url, "host", {
|
|
1171
|
+
configurable: true,
|
|
1172
|
+
enumerable: hostDesc.enumerable,
|
|
1173
|
+
get: () => hostOfUrl(url),
|
|
1174
|
+
set: (v) => {
|
|
1175
|
+
if (hostDesc.set) hostDesc.set.call(url, v);
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
return url;
|
|
1184
|
+
}
|
|
1185
|
+
CompatURL.prototype = NativeURL.prototype;
|
|
1186
|
+
for (const key of Object.getOwnPropertyNames(NativeURL)) {
|
|
1187
|
+
if (key === "length" || key === "name" || key === "prototype") continue;
|
|
1188
|
+
try {
|
|
1189
|
+
CompatURL[key] = NativeURL[key];
|
|
1190
|
+
} catch {
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
win.URL = CompatURL;
|
|
1194
|
+
return "installed";
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1104
1197
|
// client/index.js
|
|
1105
1198
|
if (typeof window !== "undefined") {
|
|
1106
1199
|
if (!window.crypto) {
|
|
@@ -1121,6 +1214,9 @@ if (typeof window !== "undefined") {
|
|
|
1121
1214
|
};
|
|
1122
1215
|
}
|
|
1123
1216
|
}
|
|
1217
|
+
var RESOURCE_URL_COMPAT = installResourceUrlCompat();
|
|
1218
|
+
if (typeof window !== "undefined") window.__dshResourceUrlCompat = RESOURCE_URL_COMPAT;
|
|
1219
|
+
var MOBILE_MAX_WIDTH = 767;
|
|
1124
1220
|
function isLocalEnvironment() {
|
|
1125
1221
|
if (typeof window === "undefined") return true;
|
|
1126
1222
|
const host = window.location.hostname || "";
|
|
@@ -2652,10 +2748,29 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
2652
2748
|
appSecret: "",
|
|
2653
2749
|
domain: platform.config.domain ?? "feishu",
|
|
2654
2750
|
botToken: "",
|
|
2655
|
-
proxy: platform.config.proxy ?? ""
|
|
2751
|
+
proxy: platform.config.proxy ?? "",
|
|
2752
|
+
// 会话级配置:留空 = 使用 DSH 默认值
|
|
2753
|
+
agentPreset: platform.config.agentPreset ?? "",
|
|
2754
|
+
cwd: platform.config.cwd ?? "",
|
|
2755
|
+
agentProvider: platform.config.agentProvider ?? "",
|
|
2756
|
+
agentModel: platform.config.agentModel ?? ""
|
|
2656
2757
|
});
|
|
2657
2758
|
}
|
|
2658
2759
|
}, [platform?.config, platformId]);
|
|
2760
|
+
const [presetOptions, setPresetOptions] = React.useState(null);
|
|
2761
|
+
React.useEffect(() => {
|
|
2762
|
+
let cancelled = false;
|
|
2763
|
+
(async () => {
|
|
2764
|
+
try {
|
|
2765
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.listAgentPresets, {});
|
|
2766
|
+
if (!cancelled && r?.ok) setPresetOptions(r.value ?? { available: false, presets: [] });
|
|
2767
|
+
} catch {
|
|
2768
|
+
}
|
|
2769
|
+
})();
|
|
2770
|
+
return () => {
|
|
2771
|
+
cancelled = true;
|
|
2772
|
+
};
|
|
2773
|
+
}, [rpcCall]);
|
|
2659
2774
|
const loadInFlightRef = React.useRef(false);
|
|
2660
2775
|
const seqRef = React.useRef(0);
|
|
2661
2776
|
const load = React.useCallback(async (quiet = false) => {
|
|
@@ -2746,7 +2861,12 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
2746
2861
|
digestIntervalSec: num(cfgDraft.digestIntervalSec, platform?.config?.digestIntervalSec ?? 300),
|
|
2747
2862
|
approvalTimeoutSec: num(cfgDraft.approvalTimeoutSec, platform?.config?.approvalTimeoutSec ?? 600),
|
|
2748
2863
|
maxMessageChars: num(cfgDraft.maxMessageChars, platform?.config?.maxMessageChars ?? 2e3),
|
|
2749
|
-
sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500)
|
|
2864
|
+
sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500),
|
|
2865
|
+
// 会话级配置:空串表示清除该设置、回落 DSH 默认值
|
|
2866
|
+
agentPreset: (cfgDraft.agentPreset ?? "").trim(),
|
|
2867
|
+
cwd: (cfgDraft.cwd ?? "").trim(),
|
|
2868
|
+
agentProvider: (cfgDraft.agentProvider ?? "").trim(),
|
|
2869
|
+
agentModel: (cfgDraft.agentModel ?? "").trim()
|
|
2750
2870
|
};
|
|
2751
2871
|
if (platformId === "qq") {
|
|
2752
2872
|
payload.appId = cfgDraft.appId.trim();
|
|
@@ -2761,7 +2881,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
2761
2881
|
}
|
|
2762
2882
|
await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
|
|
2763
2883
|
}, [act, cfgDraft, platformId]);
|
|
2764
|
-
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 ?? "")) || platformId === "telegram" && (cfgDraft.botToken !== "" || cfgDraft.proxy !== (platform.config.proxy ?? "")));
|
|
2884
|
+
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 || (cfgDraft.agentPreset ?? "") !== (platform.config.agentPreset ?? "") || (cfgDraft.cwd ?? "") !== (platform.config.cwd ?? "") || (cfgDraft.agentProvider ?? "") !== (platform.config.agentProvider ?? "") || (cfgDraft.agentModel ?? "") !== (platform.config.agentModel ?? "") || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")) || platformId === "feishu" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.appSecret !== (platform.config.appSecret ?? "")) || platformId === "telegram" && (cfgDraft.botToken !== "" || cfgDraft.proxy !== (platform.config.proxy ?? "")));
|
|
2765
2885
|
if (!platform && !err) {
|
|
2766
2886
|
return React.createElement(
|
|
2767
2887
|
"div",
|
|
@@ -3006,6 +3126,121 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
3006
3126
|
}),
|
|
3007
3127
|
React.createElement("span", null, "\u7FA4\u804A\u81EA\u52A8\u6388\u6743\uFF08\u65B0\u7FA4\u9996\u6B21 @\u673A\u5668\u4EBA \u81EA\u52A8\u52A0\u5165\u767D\u540D\u5355\uFF0C\u9ED8\u8BA4\u5173\u95ED\uFF09")
|
|
3008
3128
|
),
|
|
3129
|
+
// 高级设置:会话级配置(工作区 / Agent 预设 / 模型路由)+ 会话节奏参数
|
|
3130
|
+
React.createElement(
|
|
3131
|
+
"div",
|
|
3132
|
+
{ style: { marginTop: 12 } },
|
|
3133
|
+
React.createElement("button", {
|
|
3134
|
+
style: s.btnGhost,
|
|
3135
|
+
onClick: () => setShowAdvanced((v) => !v)
|
|
3136
|
+
}, showAdvanced ? "\u6536\u8D77\u9AD8\u7EA7\u8BBE\u7F6E" : "\u2699\uFE0F \u9AD8\u7EA7\u8BBE\u7F6E"),
|
|
3137
|
+
showAdvanced && React.createElement(
|
|
3138
|
+
"div",
|
|
3139
|
+
{ style: { ...s.block, marginTop: 8, display: "flex", flexDirection: "column", gap: 10 } },
|
|
3140
|
+
React.createElement(
|
|
3141
|
+
"div",
|
|
3142
|
+
{ style: { ...s.muted, fontSize: 12, lineHeight: 1.6 } },
|
|
3143
|
+
"\u4EE5\u4E0B\u8BBE\u7F6E\u6309\u5E73\u53F0\u4FDD\u5B58\uFF0C\u53EA\u5F71\u54CD\u8BE5\u5E73\u53F0\u901A\u8FC7 Bot \u65B0\u5EFA\u7684\u4F1A\u8BDD\uFF08\u7559\u7A7A = \u4F7F\u7528 DSH \u9ED8\u8BA4\u503C\uFF09\u3002"
|
|
3144
|
+
),
|
|
3145
|
+
// Agent 预设:决定远程会话挂载哪些工具/技能
|
|
3146
|
+
React.createElement(
|
|
3147
|
+
"div",
|
|
3148
|
+
null,
|
|
3149
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "Agent \u9884\u8BBE \u2014 \u51B3\u5B9A\u8FDC\u7A0B\u4F1A\u8BDD\u53EF\u7528\u7684\u5DE5\u5177\u4E0E\u6280\u80FD\uFF0C\u7559\u7A7A\u7528 DSH \u9ED8\u8BA4\u9884\u8BBE"),
|
|
3150
|
+
React.createElement("input", {
|
|
3151
|
+
style: { ...s.input, width: "100%" },
|
|
3152
|
+
list: "dsh-bridge-agent-presets",
|
|
3153
|
+
placeholder: presetOptions?.default ? `\u7559\u7A7A = DSH \u9ED8\u8BA4\uFF08${presetOptions.default}\uFF09` : "\u7559\u7A7A = DSH \u9ED8\u8BA4\u9884\u8BBE",
|
|
3154
|
+
value: cfgDraft?.agentPreset ?? "",
|
|
3155
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, agentPreset: e.target.value }))
|
|
3156
|
+
}),
|
|
3157
|
+
React.createElement(
|
|
3158
|
+
"datalist",
|
|
3159
|
+
{ id: "dsh-bridge-agent-presets" },
|
|
3160
|
+
(presetOptions?.presets ?? []).map((p) => React.createElement("option", { key: p.id, value: p.id }, p.name && p.name !== p.id ? p.name : null))
|
|
3161
|
+
),
|
|
3162
|
+
React.createElement(
|
|
3163
|
+
"div",
|
|
3164
|
+
{ style: { ...s.muted, fontSize: 11, marginTop: 4 } },
|
|
3165
|
+
presetOptions == null ? "\u6B63\u5728\u8BFB\u53D6\u672C\u673A\u53EF\u7528\u9884\u8BBE\u2026" : presetOptions.available ? `\u672C\u673A\u53EF\u7528\uFF1A${(presetOptions.presets ?? []).map((p) => p.id).join(" / ") || "\uFF08\u65E0\uFF09"}` : "\u5F53\u524D DSH \u7248\u672C\u672A\u63D0\u4F9B\u9884\u8BBE\u5217\u8868\uFF0C\u8BF7\u624B\u52A8\u586B\u5199\u9884\u8BBE\u540D"
|
|
3166
|
+
)
|
|
3167
|
+
),
|
|
3168
|
+
// 工作区
|
|
3169
|
+
React.createElement(
|
|
3170
|
+
"div",
|
|
3171
|
+
null,
|
|
3172
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5DE5\u4F5C\u533A\u76EE\u5F55 \u2014 \u8BE5\u5E73\u53F0 /new \u65B0\u5EFA\u4F1A\u8BDD\u7684\u9ED8\u8BA4\u76EE\u5F55\uFF0C\u7559\u7A7A\u7528\u9996\u4E2A\u5DF2\u6CE8\u518C\u5DE5\u4F5C\u533A"),
|
|
3173
|
+
React.createElement("input", {
|
|
3174
|
+
style: { ...s.input, width: "100%" },
|
|
3175
|
+
placeholder: "\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /home/me/project \u6216 D:\\project",
|
|
3176
|
+
value: cfgDraft?.cwd ?? "",
|
|
3177
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, cwd: e.target.value }))
|
|
3178
|
+
})
|
|
3179
|
+
),
|
|
3180
|
+
// 模型路由
|
|
3181
|
+
React.createElement(
|
|
3182
|
+
"div",
|
|
3183
|
+
{ style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 8 } },
|
|
3184
|
+
React.createElement(
|
|
3185
|
+
"div",
|
|
3186
|
+
null,
|
|
3187
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u6A21\u578B\u63D0\u4F9B\u65B9\uFF08\u53EF\u9009\uFF09"),
|
|
3188
|
+
React.createElement("input", {
|
|
3189
|
+
style: { ...s.input, width: "100%" },
|
|
3190
|
+
placeholder: "\u7559\u7A7A = DSH \u9ED8\u8BA4",
|
|
3191
|
+
value: cfgDraft?.agentProvider ?? "",
|
|
3192
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, agentProvider: e.target.value }))
|
|
3193
|
+
})
|
|
3194
|
+
),
|
|
3195
|
+
React.createElement(
|
|
3196
|
+
"div",
|
|
3197
|
+
null,
|
|
3198
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u6A21\u578B\uFF08\u53EF\u9009\uFF09"),
|
|
3199
|
+
React.createElement("input", {
|
|
3200
|
+
style: { ...s.input, width: "100%" },
|
|
3201
|
+
placeholder: "\u7559\u7A7A = DSH \u9ED8\u8BA4",
|
|
3202
|
+
value: cfgDraft?.agentModel ?? "",
|
|
3203
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, agentModel: e.target.value }))
|
|
3204
|
+
})
|
|
3205
|
+
)
|
|
3206
|
+
),
|
|
3207
|
+
// 会话节奏参数
|
|
3208
|
+
React.createElement(
|
|
3209
|
+
"div",
|
|
3210
|
+
{ style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 8 } },
|
|
3211
|
+
[
|
|
3212
|
+
["digestIntervalSec", "\u8FDB\u5EA6\u6458\u8981\u95F4\u9694\uFF08\u79D2\uFF09"],
|
|
3213
|
+
["approvalTimeoutSec", "\u5BA1\u6279\u8D85\u65F6\uFF08\u79D2\uFF09"],
|
|
3214
|
+
["maxMessageChars", "\u5355\u6761\u6D88\u606F\u4E0A\u9650\uFF08\u5B57\u7B26\uFF09"],
|
|
3215
|
+
["sendChunkDelayMs", "\u5206\u5757\u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09"]
|
|
3216
|
+
].map(([key, label]) => React.createElement(
|
|
3217
|
+
"div",
|
|
3218
|
+
{ key },
|
|
3219
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, label),
|
|
3220
|
+
React.createElement("input", {
|
|
3221
|
+
style: { ...s.input, width: "100%" },
|
|
3222
|
+
inputMode: "numeric",
|
|
3223
|
+
value: cfgDraft?.[key] ?? "",
|
|
3224
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, [key]: e.target.value }))
|
|
3225
|
+
})
|
|
3226
|
+
))
|
|
3227
|
+
),
|
|
3228
|
+
React.createElement(
|
|
3229
|
+
"div",
|
|
3230
|
+
{ style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" } },
|
|
3231
|
+
React.createElement("button", {
|
|
3232
|
+
style: { ...s.btnPri, opacity: busy || !cfgDirty ? 0.5 : 1 },
|
|
3233
|
+
onClick: saveConfig,
|
|
3234
|
+
disabled: busy || !cfgDirty
|
|
3235
|
+
}, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u9AD8\u7EA7\u8BBE\u7F6E"),
|
|
3236
|
+
React.createElement("button", {
|
|
3237
|
+
style: { ...s.btnGhost },
|
|
3238
|
+
onClick: resetDefaults,
|
|
3239
|
+
disabled: busy
|
|
3240
|
+
}, "\u6062\u590D\u63A8\u8350\u8282\u594F\u53C2\u6570")
|
|
3241
|
+
)
|
|
3242
|
+
)
|
|
3243
|
+
),
|
|
3009
3244
|
// 飞书 / Telegram 扫码直达对话引导卡片
|
|
3010
3245
|
(platformId === "feishu" || platformId === "telegram") && platform.botQr && React.createElement(
|
|
3011
3246
|
"div",
|
|
@@ -3558,7 +3793,12 @@ function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
|
|
|
3558
3793
|
setRestarting(true);
|
|
3559
3794
|
setStatus({ phase: "restarting", text: T.restarting });
|
|
3560
3795
|
try {
|
|
3561
|
-
await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
3796
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
3797
|
+
if (r && r.ok === false) {
|
|
3798
|
+
setStatus({ phase: "timeout", text: `${T.timeout}\uFF1A${r.error || "\u672A\u77E5\u539F\u56E0"}` });
|
|
3799
|
+
setRestarting(false);
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3562
3802
|
} catch {
|
|
3563
3803
|
}
|
|
3564
3804
|
setStatus({ phase: "reconnecting", text: T.reconnecting });
|
|
@@ -5123,7 +5363,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5123
5363
|
if (document.body.classList.contains("dsh-drawer-open")) {
|
|
5124
5364
|
document.body.classList.remove("dsh-drawer-open");
|
|
5125
5365
|
}
|
|
5126
|
-
if (typeof window !== "undefined" && window.innerWidth <=
|
|
5366
|
+
if (typeof window !== "undefined" && window.innerWidth <= MOBILE_MAX_WIDTH) {
|
|
5127
5367
|
document.body.classList.remove("dsh-workbench-open");
|
|
5128
5368
|
const openPanels = document.querySelectorAll('div[class*="nArs4W_panel"]:not([class*="panelHidden"]), div[class*="workbench_panel"]:not([class*="panelHidden"])');
|
|
5129
5369
|
openPanels.forEach((p) => p.classList.add("nArs4W_panelHidden"));
|
|
@@ -5132,7 +5372,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5132
5372
|
}
|
|
5133
5373
|
const ensurePanelCloseButton = () => {
|
|
5134
5374
|
if (typeof window === "undefined") return;
|
|
5135
|
-
if (window.innerWidth >
|
|
5375
|
+
if (window.innerWidth > MOBILE_MAX_WIDTH) {
|
|
5136
5376
|
document.querySelectorAll(".dsh-mobile-panel-close-btn").forEach((btn) => btn.remove());
|
|
5137
5377
|
return;
|
|
5138
5378
|
}
|
|
@@ -5158,14 +5398,14 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5158
5398
|
panelObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
|
|
5159
5399
|
window.addEventListener("resize", ensurePanelCloseButton);
|
|
5160
5400
|
document.addEventListener("click", (e) => {
|
|
5161
|
-
if (typeof window === "undefined" || window.innerWidth >
|
|
5401
|
+
if (typeof window === "undefined" || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
5162
5402
|
const trigger = e.target.closest('button[aria-label*="\u9762\u677F"], button[aria-label*="\u5DE5\u4F5C\u533A"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
5163
5403
|
if (trigger && !trigger.classList.contains("dsh-mobile-panel-close-btn") && !trigger.classList.contains("dsh-header-menu-btn") && !trigger.classList.contains("dsh-header-new-btn")) {
|
|
5164
5404
|
document.body.classList.add("dsh-workbench-open");
|
|
5165
5405
|
}
|
|
5166
5406
|
}, true);
|
|
5167
5407
|
document.addEventListener("click", (e) => {
|
|
5168
|
-
if (typeof window === "undefined" || window.innerWidth >
|
|
5408
|
+
if (typeof window === "undefined" || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
5169
5409
|
const toggle = e.target.closest('button[aria-label*="\u6536\u8D77\u4FA7\u8FB9\u680F"], button[title*="\u6536\u8D77\u4FA7\u8FB9\u680F"]');
|
|
5170
5410
|
if (toggle) {
|
|
5171
5411
|
document.body.classList.remove("dsh-drawer-open");
|
|
@@ -6007,7 +6247,7 @@ function setupIosKeyboardAdapter() {
|
|
|
6007
6247
|
}
|
|
6008
6248
|
function setupComposerCollapse() {
|
|
6009
6249
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
6010
|
-
if (window.innerWidth >
|
|
6250
|
+
if (window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
6011
6251
|
const LS_KEY = "dsh-composer-fold";
|
|
6012
6252
|
let bar = null;
|
|
6013
6253
|
let busy = false;
|
package/client/index.js
CHANGED
|
@@ -29,6 +29,17 @@ if (typeof window !== 'undefined') {
|
|
|
29
29
|
|
|
30
30
|
import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from '../lib/bridge-rpc-constants.js';
|
|
31
31
|
|
|
32
|
+
import { installResourceUrlCompat } from './resource-url-compat.js';
|
|
33
|
+
|
|
34
|
+
const RESOURCE_URL_COMPAT = installResourceUrlCompat();
|
|
35
|
+
if (typeof window !== 'undefined') window.__dshResourceUrlCompat = RESOURCE_URL_COMPAT;
|
|
36
|
+
|
|
37
|
+
// 移动端断点上限:宿主用 viewportWidth < 768 判定右侧栏自动全屏
|
|
38
|
+
// (@deepseek-ai/dsh-client-ui-sidebar-right 的 autoFullscreen),
|
|
39
|
+
// 故桥的移动端分支一律取「<= 767」,与 mobile-styles.js 的 @media (max-width: 767px) 对齐,
|
|
40
|
+
// 避免 768px 单点上"桥渲染顶栏、宿主却按桌面(push)布局"的错位。
|
|
41
|
+
const MOBILE_MAX_WIDTH = 767;
|
|
42
|
+
|
|
32
43
|
function isLocalEnvironment() {
|
|
33
44
|
if (typeof window === 'undefined') return true;
|
|
34
45
|
const host = window.location.hostname || '';
|
|
@@ -1396,10 +1407,28 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
1396
1407
|
domain: platform.config.domain ?? 'feishu',
|
|
1397
1408
|
botToken: '',
|
|
1398
1409
|
proxy: platform.config.proxy ?? '',
|
|
1410
|
+
// 会话级配置:留空 = 使用 DSH 默认值
|
|
1411
|
+
agentPreset: platform.config.agentPreset ?? '',
|
|
1412
|
+
cwd: platform.config.cwd ?? '',
|
|
1413
|
+
agentProvider: platform.config.agentProvider ?? '',
|
|
1414
|
+
agentModel: platform.config.agentModel ?? '',
|
|
1399
1415
|
});
|
|
1400
1416
|
}
|
|
1401
1417
|
}, [platform?.config, platformId]);
|
|
1402
1418
|
|
|
1419
|
+
// 本机可用的 DSH agent preset(旧版本 DSH 返回 available:false → 退化为手填)
|
|
1420
|
+
const [presetOptions, setPresetOptions] = React.useState(null);
|
|
1421
|
+
React.useEffect(() => {
|
|
1422
|
+
let cancelled = false;
|
|
1423
|
+
(async () => {
|
|
1424
|
+
try {
|
|
1425
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.listAgentPresets, {});
|
|
1426
|
+
if (!cancelled && r?.ok) setPresetOptions(r.value ?? { available: false, presets: [] });
|
|
1427
|
+
} catch { /* 读取失败保持手填 */ }
|
|
1428
|
+
})();
|
|
1429
|
+
return () => { cancelled = true; };
|
|
1430
|
+
}, [rpcCall]);
|
|
1431
|
+
|
|
1403
1432
|
const loadInFlightRef = React.useRef(false);
|
|
1404
1433
|
const seqRef = React.useRef(0);
|
|
1405
1434
|
const load = React.useCallback(async (quiet = false) => {
|
|
@@ -1507,6 +1536,11 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
1507
1536
|
approvalTimeoutSec: num(cfgDraft.approvalTimeoutSec, platform?.config?.approvalTimeoutSec ?? 600),
|
|
1508
1537
|
maxMessageChars: num(cfgDraft.maxMessageChars, platform?.config?.maxMessageChars ?? 2000),
|
|
1509
1538
|
sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500),
|
|
1539
|
+
// 会话级配置:空串表示清除该设置、回落 DSH 默认值
|
|
1540
|
+
agentPreset: (cfgDraft.agentPreset ?? '').trim(),
|
|
1541
|
+
cwd: (cfgDraft.cwd ?? '').trim(),
|
|
1542
|
+
agentProvider: (cfgDraft.agentProvider ?? '').trim(),
|
|
1543
|
+
agentModel: (cfgDraft.agentModel ?? '').trim(),
|
|
1510
1544
|
};
|
|
1511
1545
|
// QQ / 飞书 / Telegram 平台额外携带凭证
|
|
1512
1546
|
if (platformId === 'qq') {
|
|
@@ -1527,6 +1561,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
1527
1561
|
Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec ||
|
|
1528
1562
|
Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars ||
|
|
1529
1563
|
Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs ||
|
|
1564
|
+
(cfgDraft.agentPreset ?? '') !== (platform.config.agentPreset ?? '') ||
|
|
1565
|
+
(cfgDraft.cwd ?? '') !== (platform.config.cwd ?? '') ||
|
|
1566
|
+
(cfgDraft.agentProvider ?? '') !== (platform.config.agentProvider ?? '') ||
|
|
1567
|
+
(cfgDraft.agentModel ?? '') !== (platform.config.agentModel ?? '') ||
|
|
1530
1568
|
(platformId === 'qq' && (
|
|
1531
1569
|
cfgDraft.appId !== (platform.config.appId ?? '') ||
|
|
1532
1570
|
cfgDraft.clientSecret !== (platform.config.clientSecret ?? '')
|
|
@@ -1747,6 +1785,99 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
|
|
|
1747
1785
|
}),
|
|
1748
1786
|
React.createElement('span', null, '群聊自动授权(新群首次 @机器人 自动加入白名单,默认关闭)'),
|
|
1749
1787
|
),
|
|
1788
|
+
// 高级设置:会话级配置(工作区 / Agent 预设 / 模型路由)+ 会话节奏参数
|
|
1789
|
+
React.createElement('div', { style: { marginTop: 12 } },
|
|
1790
|
+
React.createElement('button', {
|
|
1791
|
+
style: s.btnGhost,
|
|
1792
|
+
onClick: () => setShowAdvanced(v => !v),
|
|
1793
|
+
}, showAdvanced ? '收起高级设置' : '⚙️ 高级设置'),
|
|
1794
|
+
showAdvanced && React.createElement('div', { style: { ...s.block, marginTop: 8, display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
1795
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 12, lineHeight: 1.6 } },
|
|
1796
|
+
'以下设置按平台保存,只影响该平台通过 Bot 新建的会话(留空 = 使用 DSH 默认值)。'),
|
|
1797
|
+
// Agent 预设:决定远程会话挂载哪些工具/技能
|
|
1798
|
+
React.createElement('div', null,
|
|
1799
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'Agent 预设 — 决定远程会话可用的工具与技能,留空用 DSH 默认预设'),
|
|
1800
|
+
React.createElement('input', {
|
|
1801
|
+
style: { ...s.input, width: '100%' },
|
|
1802
|
+
list: 'dsh-bridge-agent-presets',
|
|
1803
|
+
placeholder: presetOptions?.default ? `留空 = DSH 默认(${presetOptions.default})` : '留空 = DSH 默认预设',
|
|
1804
|
+
value: cfgDraft?.agentPreset ?? '',
|
|
1805
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, agentPreset: e.target.value })),
|
|
1806
|
+
}),
|
|
1807
|
+
React.createElement('datalist', { id: 'dsh-bridge-agent-presets' },
|
|
1808
|
+
(presetOptions?.presets ?? []).map((p) =>
|
|
1809
|
+
React.createElement('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? p.name : null)),
|
|
1810
|
+
),
|
|
1811
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
|
|
1812
|
+
presetOptions == null
|
|
1813
|
+
? '正在读取本机可用预设…'
|
|
1814
|
+
: presetOptions.available
|
|
1815
|
+
? `本机可用:${(presetOptions.presets ?? []).map((p) => p.id).join(' / ') || '(无)'}`
|
|
1816
|
+
: '当前 DSH 版本未提供预设列表,请手动填写预设名'),
|
|
1817
|
+
),
|
|
1818
|
+
// 工作区
|
|
1819
|
+
React.createElement('div', null,
|
|
1820
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '工作区目录 — 该平台 /new 新建会话的默认目录,留空用首个已注册工作区'),
|
|
1821
|
+
React.createElement('input', {
|
|
1822
|
+
style: { ...s.input, width: '100%' },
|
|
1823
|
+
placeholder: '绝对路径,例如 /home/me/project 或 D:\\project',
|
|
1824
|
+
value: cfgDraft?.cwd ?? '',
|
|
1825
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, cwd: e.target.value })),
|
|
1826
|
+
}),
|
|
1827
|
+
),
|
|
1828
|
+
// 模型路由
|
|
1829
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 8 } },
|
|
1830
|
+
React.createElement('div', null,
|
|
1831
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '模型提供方(可选)'),
|
|
1832
|
+
React.createElement('input', {
|
|
1833
|
+
style: { ...s.input, width: '100%' },
|
|
1834
|
+
placeholder: '留空 = DSH 默认',
|
|
1835
|
+
value: cfgDraft?.agentProvider ?? '',
|
|
1836
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, agentProvider: e.target.value })),
|
|
1837
|
+
}),
|
|
1838
|
+
),
|
|
1839
|
+
React.createElement('div', null,
|
|
1840
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '模型(可选)'),
|
|
1841
|
+
React.createElement('input', {
|
|
1842
|
+
style: { ...s.input, width: '100%' },
|
|
1843
|
+
placeholder: '留空 = DSH 默认',
|
|
1844
|
+
value: cfgDraft?.agentModel ?? '',
|
|
1845
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, agentModel: e.target.value })),
|
|
1846
|
+
}),
|
|
1847
|
+
),
|
|
1848
|
+
),
|
|
1849
|
+
// 会话节奏参数
|
|
1850
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 8 } },
|
|
1851
|
+
[
|
|
1852
|
+
['digestIntervalSec', '进度摘要间隔(秒)'],
|
|
1853
|
+
['approvalTimeoutSec', '审批超时(秒)'],
|
|
1854
|
+
['maxMessageChars', '单条消息上限(字符)'],
|
|
1855
|
+
['sendChunkDelayMs', '分块发送间隔(毫秒)'],
|
|
1856
|
+
].map(([key, label]) =>
|
|
1857
|
+
React.createElement('div', { key },
|
|
1858
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, label),
|
|
1859
|
+
React.createElement('input', {
|
|
1860
|
+
style: { ...s.input, width: '100%' },
|
|
1861
|
+
inputMode: 'numeric',
|
|
1862
|
+
value: cfgDraft?.[key] ?? '',
|
|
1863
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, [key]: e.target.value })),
|
|
1864
|
+
}),
|
|
1865
|
+
)),
|
|
1866
|
+
),
|
|
1867
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1868
|
+
React.createElement('button', {
|
|
1869
|
+
style: { ...s.btnPri, opacity: (busy || !cfgDirty) ? 0.5 : 1 },
|
|
1870
|
+
onClick: saveConfig,
|
|
1871
|
+
disabled: busy || !cfgDirty,
|
|
1872
|
+
}, busy ? '保存中…' : '保存高级设置'),
|
|
1873
|
+
React.createElement('button', {
|
|
1874
|
+
style: { ...s.btnGhost },
|
|
1875
|
+
onClick: resetDefaults,
|
|
1876
|
+
disabled: busy,
|
|
1877
|
+
}, '恢复推荐节奏参数'),
|
|
1878
|
+
),
|
|
1879
|
+
),
|
|
1880
|
+
),
|
|
1750
1881
|
// 飞书 / Telegram 扫码直达对话引导卡片
|
|
1751
1882
|
(platformId === 'feishu' || platformId === 'telegram') && platform.botQr && React.createElement('div', {
|
|
1752
1883
|
style: {
|
|
@@ -2207,9 +2338,16 @@ function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
|
|
|
2207
2338
|
setRestarting(true);
|
|
2208
2339
|
setStatus({ phase: 'restarting', text: T.restarting });
|
|
2209
2340
|
try {
|
|
2210
|
-
await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
2341
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
2342
|
+
// 宿主已明确告知"没能安排重启"(例如 systemd 调用失败、助手都派生不出来):
|
|
2343
|
+
// 这时绝不能再假装"正在重连"——直接报错并停下,否则用户只会看到永远转圈。
|
|
2344
|
+
if (r && r.ok === false) {
|
|
2345
|
+
setStatus({ phase: 'timeout', text: `${T.timeout}:${r.error || '未知原因'}` });
|
|
2346
|
+
setRestarting(false);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2211
2349
|
} catch {
|
|
2212
|
-
//
|
|
2350
|
+
// 服务可能瞬间关闭导致连接断开,忽略;下面的轮询会判断是否真的起来了
|
|
2213
2351
|
}
|
|
2214
2352
|
|
|
2215
2353
|
setStatus({ phase: 'reconnecting', text: T.reconnecting });
|
|
@@ -3705,7 +3843,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3705
3843
|
document.body.classList.remove('dsh-drawer-open');
|
|
3706
3844
|
}
|
|
3707
3845
|
// 仅在移动端切换会话时自动收起右侧面板回到对话(PC端绝不干扰)
|
|
3708
|
-
if (typeof window !== 'undefined' && window.innerWidth <=
|
|
3846
|
+
if (typeof window !== 'undefined' && window.innerWidth <= MOBILE_MAX_WIDTH) {
|
|
3709
3847
|
document.body.classList.remove('dsh-workbench-open');
|
|
3710
3848
|
const openPanels = document.querySelectorAll('div[class*="nArs4W_panel"]:not([class*="panelHidden"]), div[class*="workbench_panel"]:not([class*="panelHidden"])');
|
|
3711
3849
|
openPanels.forEach((p) => p.classList.add('nArs4W_panelHidden'));
|
|
@@ -3713,10 +3851,10 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3713
3851
|
});
|
|
3714
3852
|
}
|
|
3715
3853
|
|
|
3716
|
-
// 1.1 移动端右侧边栏 / Workbench 面板管理:仅在移动端(<=
|
|
3854
|
+
// 1.1 移动端右侧边栏 / Workbench 面板管理:仅在移动端(<= 767px)挂载“返回对话”收起按钮,PC端保持纯净
|
|
3717
3855
|
const ensurePanelCloseButton = () => {
|
|
3718
3856
|
if (typeof window === 'undefined') return;
|
|
3719
|
-
if (window.innerWidth >
|
|
3857
|
+
if (window.innerWidth > MOBILE_MAX_WIDTH) {
|
|
3720
3858
|
document.querySelectorAll('.dsh-mobile-panel-close-btn').forEach(btn => btn.remove());
|
|
3721
3859
|
return;
|
|
3722
3860
|
}
|
|
@@ -3745,7 +3883,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3745
3883
|
|
|
3746
3884
|
// 移动端点击面板/工作区触发按钮时,自动激活 dsh-workbench-open
|
|
3747
3885
|
document.addEventListener('click', (e) => {
|
|
3748
|
-
if (typeof window === 'undefined' || window.innerWidth >
|
|
3886
|
+
if (typeof window === 'undefined' || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
3749
3887
|
const trigger = e.target.closest('button[aria-label*="面板"], button[aria-label*="工作区"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
3750
3888
|
if (trigger && !trigger.classList.contains('dsh-mobile-panel-close-btn') && !trigger.classList.contains('dsh-header-menu-btn') && !trigger.classList.contains('dsh-header-new-btn')) {
|
|
3751
3889
|
document.body.classList.add('dsh-workbench-open');
|
|
@@ -3754,7 +3892,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3754
3892
|
|
|
3755
3893
|
// 移动端点击 DSH 自带的收起侧边栏图标时,自动收起抽屉
|
|
3756
3894
|
document.addEventListener('click', (e) => {
|
|
3757
|
-
if (typeof window === 'undefined' || window.innerWidth >
|
|
3895
|
+
if (typeof window === 'undefined' || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
3758
3896
|
const toggle = e.target.closest('button[aria-label*="收起侧边栏"], button[title*="收起侧边栏"]');
|
|
3759
3897
|
if (toggle) {
|
|
3760
3898
|
document.body.classList.remove('dsh-drawer-open');
|
|
@@ -4764,7 +4902,7 @@ function setupIosKeyboardAdapter() {
|
|
|
4764
4902
|
// 折叠态记忆到 localStorage(dsh-composer-fold),跨会话保持用户偏好。
|
|
4765
4903
|
function setupComposerCollapse() {
|
|
4766
4904
|
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
4767
|
-
if (window.innerWidth >
|
|
4905
|
+
if (window.innerWidth > MOBILE_MAX_WIDTH) return; // 仅移动端
|
|
4768
4906
|
const LS_KEY = 'dsh-composer-fold';
|
|
4769
4907
|
|
|
4770
4908
|
let bar = null; // 折叠态底部"点击输入"细条
|