@wenbin_wb/dsh-bridge 2.10.9 → 2.10.10

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 CHANGED
@@ -4,6 +4,21 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [v2.10.10] - 2026-09-14
8
+
9
+ ### ✨ 新功能
10
+
11
+ - **设置页新增「高级设置」**(每个 IM 平台卡片内):可直接配置该平台远程会话的 **Agent 预设**(下拉列出本机可用预设,留空使用 DSH 默认预设)、**工作区目录**、**模型提供方 / 模型**,以及进度摘要间隔、审批超时、单条消息上限、分块发送间隔。保存后立即生效,并随宿主重启保留——此前这四个会话级配置没有任何界面入口,只能手改 `config.json`,节奏参数的面板也已在早前重构中丢失。
12
+
13
+ ### 🐞 修复
14
+
15
+ - **DSH 0.1.5 系列上远程 IM 会话工具集残缺**(issue #40):DSH 从 `0.1.5` 起把 agent 侧的工具、提示词与技能目录整体挪到了 agent preset 后面(宿主层不再挂载 `tool-fs` / `tool-bash` / `tool-jobs` / `tool-skill` 等),改由每个会话在创建时挂载预设提供。桥此前只把预设名写进会话元数据、从未真正挂载,会话因此落在「空预设层」——表现为远程 Agent 缺少工具(例如没有读文件的工具)。现在新建与恢复会话都会真实挂载预设:未配置时使用 DSH 自身的默认预设;配置的预设在本机已不存在时回退默认预设,并在会话创建提示里明确告知,不再静默降级。
16
+ - **宿主重启后给已有会话发消息报 `session "..." already exists`、随后卡在「当前没有活动会话」**(issue #39):判断会话是否已持久化时沿用了旧的数据结构假设,而 DSH 0.1.5 的会话列表返回的是 `{ header, revision, sizeBytes }` 快照,`entry.id` 恒为空——已存在的历史会话被误判成新会话,走 `agents.create` 建同名会话而失败。现按真实结构取会话头,重启后能正常恢复续聊。
17
+ - **远程会话的工作区与预设配置重启即丢**(issue #40):`cwd`、`agentPreset`、`agentProvider`、`agentModel` 写进配置后,宿主重启即失效(恢复链路只回写了白名单里的少数字段),远程会话会掉到默认目录与默认预设。现已在重启恢复与配置备份导入时一并读回;同时补上 QQ / 飞书此前遗漏的摘要间隔、审批超时、分块延时恢复。配置的工作区目录不可用(被删、换机器、写成相对路径)时回落到已注册工作区并提示,不再把会话建在不存在的目录上。
18
+ - **`/rename` 改名重启即失效**:此前调用的是 `sessionPersistence.update()`——DSH 的持久化服务根本没有这个方法,被可选链静默跳过,改名只改了内存对象。现改用 DSH 原生会话标题服务(追加 `session/title` 事件并落盘),重启与 Web 侧边栏都能看到新标题;会话尚未恢复时会明确提示而不是假报成功。
19
+
20
+ ---
21
+
7
22
  ## [v2.10.9] - 2026-09-12
8
23
 
9
24
  ### 🐞 修复
package/client/client.js CHANGED
@@ -1091,6 +1091,8 @@ var BRIDGE_ENDPOINTS = {
1091
1091
  platformStop: "platformStop",
1092
1092
  platformStart: "platformStart",
1093
1093
  platformUnbind: "platformUnbind",
1094
+ // 本机可用的 DSH agent preset(设置页下拉用;老版本 DSH 返回 available:false)
1095
+ listAgentPresets: "listAgentPresets",
1094
1096
  // 微信 Bot(v1.x 向后兼容别名,deprecated)
1095
1097
  wechatGetStatus: "wechatGetStatus",
1096
1098
  wechatLogin: "wechatLogin",
@@ -2652,10 +2654,29 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
2652
2654
  appSecret: "",
2653
2655
  domain: platform.config.domain ?? "feishu",
2654
2656
  botToken: "",
2655
- proxy: platform.config.proxy ?? ""
2657
+ proxy: platform.config.proxy ?? "",
2658
+ // 会话级配置:留空 = 使用 DSH 默认值
2659
+ agentPreset: platform.config.agentPreset ?? "",
2660
+ cwd: platform.config.cwd ?? "",
2661
+ agentProvider: platform.config.agentProvider ?? "",
2662
+ agentModel: platform.config.agentModel ?? ""
2656
2663
  });
2657
2664
  }
2658
2665
  }, [platform?.config, platformId]);
2666
+ const [presetOptions, setPresetOptions] = React.useState(null);
2667
+ React.useEffect(() => {
2668
+ let cancelled = false;
2669
+ (async () => {
2670
+ try {
2671
+ const r = await rpcCall(BRIDGE_ENDPOINTS.listAgentPresets, {});
2672
+ if (!cancelled && r?.ok) setPresetOptions(r.value ?? { available: false, presets: [] });
2673
+ } catch {
2674
+ }
2675
+ })();
2676
+ return () => {
2677
+ cancelled = true;
2678
+ };
2679
+ }, [rpcCall]);
2659
2680
  const loadInFlightRef = React.useRef(false);
2660
2681
  const seqRef = React.useRef(0);
2661
2682
  const load = React.useCallback(async (quiet = false) => {
@@ -2746,7 +2767,12 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
2746
2767
  digestIntervalSec: num(cfgDraft.digestIntervalSec, platform?.config?.digestIntervalSec ?? 300),
2747
2768
  approvalTimeoutSec: num(cfgDraft.approvalTimeoutSec, platform?.config?.approvalTimeoutSec ?? 600),
2748
2769
  maxMessageChars: num(cfgDraft.maxMessageChars, platform?.config?.maxMessageChars ?? 2e3),
2749
- sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500)
2770
+ sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500),
2771
+ // 会话级配置:空串表示清除该设置、回落 DSH 默认值
2772
+ agentPreset: (cfgDraft.agentPreset ?? "").trim(),
2773
+ cwd: (cfgDraft.cwd ?? "").trim(),
2774
+ agentProvider: (cfgDraft.agentProvider ?? "").trim(),
2775
+ agentModel: (cfgDraft.agentModel ?? "").trim()
2750
2776
  };
2751
2777
  if (platformId === "qq") {
2752
2778
  payload.appId = cfgDraft.appId.trim();
@@ -2761,7 +2787,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
2761
2787
  }
2762
2788
  await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
2763
2789
  }, [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 ?? "")));
2790
+ 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
2791
  if (!platform && !err) {
2766
2792
  return React.createElement(
2767
2793
  "div",
@@ -3006,6 +3032,121 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
3006
3032
  }),
3007
3033
  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
3034
  ),
3035
+ // 高级设置:会话级配置(工作区 / Agent 预设 / 模型路由)+ 会话节奏参数
3036
+ React.createElement(
3037
+ "div",
3038
+ { style: { marginTop: 12 } },
3039
+ React.createElement("button", {
3040
+ style: s.btnGhost,
3041
+ onClick: () => setShowAdvanced((v) => !v)
3042
+ }, showAdvanced ? "\u6536\u8D77\u9AD8\u7EA7\u8BBE\u7F6E" : "\u2699\uFE0F \u9AD8\u7EA7\u8BBE\u7F6E"),
3043
+ showAdvanced && React.createElement(
3044
+ "div",
3045
+ { style: { ...s.block, marginTop: 8, display: "flex", flexDirection: "column", gap: 10 } },
3046
+ React.createElement(
3047
+ "div",
3048
+ { style: { ...s.muted, fontSize: 12, lineHeight: 1.6 } },
3049
+ "\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"
3050
+ ),
3051
+ // Agent 预设:决定远程会话挂载哪些工具/技能
3052
+ React.createElement(
3053
+ "div",
3054
+ null,
3055
+ 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"),
3056
+ React.createElement("input", {
3057
+ style: { ...s.input, width: "100%" },
3058
+ list: "dsh-bridge-agent-presets",
3059
+ placeholder: presetOptions?.default ? `\u7559\u7A7A = DSH \u9ED8\u8BA4\uFF08${presetOptions.default}\uFF09` : "\u7559\u7A7A = DSH \u9ED8\u8BA4\u9884\u8BBE",
3060
+ value: cfgDraft?.agentPreset ?? "",
3061
+ onChange: (e) => setCfgDraft((d) => ({ ...d, agentPreset: e.target.value }))
3062
+ }),
3063
+ React.createElement(
3064
+ "datalist",
3065
+ { id: "dsh-bridge-agent-presets" },
3066
+ (presetOptions?.presets ?? []).map((p) => React.createElement("option", { key: p.id, value: p.id }, p.name && p.name !== p.id ? p.name : null))
3067
+ ),
3068
+ React.createElement(
3069
+ "div",
3070
+ { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
3071
+ 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"
3072
+ )
3073
+ ),
3074
+ // 工作区
3075
+ React.createElement(
3076
+ "div",
3077
+ null,
3078
+ 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"),
3079
+ React.createElement("input", {
3080
+ style: { ...s.input, width: "100%" },
3081
+ placeholder: "\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /home/me/project \u6216 D:\\project",
3082
+ value: cfgDraft?.cwd ?? "",
3083
+ onChange: (e) => setCfgDraft((d) => ({ ...d, cwd: e.target.value }))
3084
+ })
3085
+ ),
3086
+ // 模型路由
3087
+ React.createElement(
3088
+ "div",
3089
+ { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 8 } },
3090
+ React.createElement(
3091
+ "div",
3092
+ null,
3093
+ React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u6A21\u578B\u63D0\u4F9B\u65B9\uFF08\u53EF\u9009\uFF09"),
3094
+ React.createElement("input", {
3095
+ style: { ...s.input, width: "100%" },
3096
+ placeholder: "\u7559\u7A7A = DSH \u9ED8\u8BA4",
3097
+ value: cfgDraft?.agentProvider ?? "",
3098
+ onChange: (e) => setCfgDraft((d) => ({ ...d, agentProvider: e.target.value }))
3099
+ })
3100
+ ),
3101
+ React.createElement(
3102
+ "div",
3103
+ null,
3104
+ React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u6A21\u578B\uFF08\u53EF\u9009\uFF09"),
3105
+ React.createElement("input", {
3106
+ style: { ...s.input, width: "100%" },
3107
+ placeholder: "\u7559\u7A7A = DSH \u9ED8\u8BA4",
3108
+ value: cfgDraft?.agentModel ?? "",
3109
+ onChange: (e) => setCfgDraft((d) => ({ ...d, agentModel: e.target.value }))
3110
+ })
3111
+ )
3112
+ ),
3113
+ // 会话节奏参数
3114
+ React.createElement(
3115
+ "div",
3116
+ { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 8 } },
3117
+ [
3118
+ ["digestIntervalSec", "\u8FDB\u5EA6\u6458\u8981\u95F4\u9694\uFF08\u79D2\uFF09"],
3119
+ ["approvalTimeoutSec", "\u5BA1\u6279\u8D85\u65F6\uFF08\u79D2\uFF09"],
3120
+ ["maxMessageChars", "\u5355\u6761\u6D88\u606F\u4E0A\u9650\uFF08\u5B57\u7B26\uFF09"],
3121
+ ["sendChunkDelayMs", "\u5206\u5757\u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09"]
3122
+ ].map(([key, label]) => React.createElement(
3123
+ "div",
3124
+ { key },
3125
+ React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, label),
3126
+ React.createElement("input", {
3127
+ style: { ...s.input, width: "100%" },
3128
+ inputMode: "numeric",
3129
+ value: cfgDraft?.[key] ?? "",
3130
+ onChange: (e) => setCfgDraft((d) => ({ ...d, [key]: e.target.value }))
3131
+ })
3132
+ ))
3133
+ ),
3134
+ React.createElement(
3135
+ "div",
3136
+ { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" } },
3137
+ React.createElement("button", {
3138
+ style: { ...s.btnPri, opacity: busy || !cfgDirty ? 0.5 : 1 },
3139
+ onClick: saveConfig,
3140
+ disabled: busy || !cfgDirty
3141
+ }, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u9AD8\u7EA7\u8BBE\u7F6E"),
3142
+ React.createElement("button", {
3143
+ style: { ...s.btnGhost },
3144
+ onClick: resetDefaults,
3145
+ disabled: busy
3146
+ }, "\u6062\u590D\u63A8\u8350\u8282\u594F\u53C2\u6570")
3147
+ )
3148
+ )
3149
+ ),
3009
3150
  // 飞书 / Telegram 扫码直达对话引导卡片
3010
3151
  (platformId === "feishu" || platformId === "telegram") && platform.botQr && React.createElement(
3011
3152
  "div",
package/client/index.js CHANGED
@@ -1396,10 +1396,28 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
1396
1396
  domain: platform.config.domain ?? 'feishu',
1397
1397
  botToken: '',
1398
1398
  proxy: platform.config.proxy ?? '',
1399
+ // 会话级配置:留空 = 使用 DSH 默认值
1400
+ agentPreset: platform.config.agentPreset ?? '',
1401
+ cwd: platform.config.cwd ?? '',
1402
+ agentProvider: platform.config.agentProvider ?? '',
1403
+ agentModel: platform.config.agentModel ?? '',
1399
1404
  });
1400
1405
  }
1401
1406
  }, [platform?.config, platformId]);
1402
1407
 
1408
+ // 本机可用的 DSH agent preset(旧版本 DSH 返回 available:false → 退化为手填)
1409
+ const [presetOptions, setPresetOptions] = React.useState(null);
1410
+ React.useEffect(() => {
1411
+ let cancelled = false;
1412
+ (async () => {
1413
+ try {
1414
+ const r = await rpcCall(BRIDGE_ENDPOINTS.listAgentPresets, {});
1415
+ if (!cancelled && r?.ok) setPresetOptions(r.value ?? { available: false, presets: [] });
1416
+ } catch { /* 读取失败保持手填 */ }
1417
+ })();
1418
+ return () => { cancelled = true; };
1419
+ }, [rpcCall]);
1420
+
1403
1421
  const loadInFlightRef = React.useRef(false);
1404
1422
  const seqRef = React.useRef(0);
1405
1423
  const load = React.useCallback(async (quiet = false) => {
@@ -1507,6 +1525,11 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
1507
1525
  approvalTimeoutSec: num(cfgDraft.approvalTimeoutSec, platform?.config?.approvalTimeoutSec ?? 600),
1508
1526
  maxMessageChars: num(cfgDraft.maxMessageChars, platform?.config?.maxMessageChars ?? 2000),
1509
1527
  sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500),
1528
+ // 会话级配置:空串表示清除该设置、回落 DSH 默认值
1529
+ agentPreset: (cfgDraft.agentPreset ?? '').trim(),
1530
+ cwd: (cfgDraft.cwd ?? '').trim(),
1531
+ agentProvider: (cfgDraft.agentProvider ?? '').trim(),
1532
+ agentModel: (cfgDraft.agentModel ?? '').trim(),
1510
1533
  };
1511
1534
  // QQ / 飞书 / Telegram 平台额外携带凭证
1512
1535
  if (platformId === 'qq') {
@@ -1527,6 +1550,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
1527
1550
  Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec ||
1528
1551
  Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars ||
1529
1552
  Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs ||
1553
+ (cfgDraft.agentPreset ?? '') !== (platform.config.agentPreset ?? '') ||
1554
+ (cfgDraft.cwd ?? '') !== (platform.config.cwd ?? '') ||
1555
+ (cfgDraft.agentProvider ?? '') !== (platform.config.agentProvider ?? '') ||
1556
+ (cfgDraft.agentModel ?? '') !== (platform.config.agentModel ?? '') ||
1530
1557
  (platformId === 'qq' && (
1531
1558
  cfgDraft.appId !== (platform.config.appId ?? '') ||
1532
1559
  cfgDraft.clientSecret !== (platform.config.clientSecret ?? '')
@@ -1747,6 +1774,99 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
1747
1774
  }),
1748
1775
  React.createElement('span', null, '群聊自动授权(新群首次 @机器人 自动加入白名单,默认关闭)'),
1749
1776
  ),
1777
+ // 高级设置:会话级配置(工作区 / Agent 预设 / 模型路由)+ 会话节奏参数
1778
+ React.createElement('div', { style: { marginTop: 12 } },
1779
+ React.createElement('button', {
1780
+ style: s.btnGhost,
1781
+ onClick: () => setShowAdvanced(v => !v),
1782
+ }, showAdvanced ? '收起高级设置' : '⚙️ 高级设置'),
1783
+ showAdvanced && React.createElement('div', { style: { ...s.block, marginTop: 8, display: 'flex', flexDirection: 'column', gap: 10 } },
1784
+ React.createElement('div', { style: { ...s.muted, fontSize: 12, lineHeight: 1.6 } },
1785
+ '以下设置按平台保存,只影响该平台通过 Bot 新建的会话(留空 = 使用 DSH 默认值)。'),
1786
+ // Agent 预设:决定远程会话挂载哪些工具/技能
1787
+ React.createElement('div', null,
1788
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'Agent 预设 — 决定远程会话可用的工具与技能,留空用 DSH 默认预设'),
1789
+ React.createElement('input', {
1790
+ style: { ...s.input, width: '100%' },
1791
+ list: 'dsh-bridge-agent-presets',
1792
+ placeholder: presetOptions?.default ? `留空 = DSH 默认(${presetOptions.default})` : '留空 = DSH 默认预设',
1793
+ value: cfgDraft?.agentPreset ?? '',
1794
+ onChange: (e) => setCfgDraft(d => ({ ...d, agentPreset: e.target.value })),
1795
+ }),
1796
+ React.createElement('datalist', { id: 'dsh-bridge-agent-presets' },
1797
+ (presetOptions?.presets ?? []).map((p) =>
1798
+ React.createElement('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? p.name : null)),
1799
+ ),
1800
+ React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
1801
+ presetOptions == null
1802
+ ? '正在读取本机可用预设…'
1803
+ : presetOptions.available
1804
+ ? `本机可用:${(presetOptions.presets ?? []).map((p) => p.id).join(' / ') || '(无)'}`
1805
+ : '当前 DSH 版本未提供预设列表,请手动填写预设名'),
1806
+ ),
1807
+ // 工作区
1808
+ React.createElement('div', null,
1809
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '工作区目录 — 该平台 /new 新建会话的默认目录,留空用首个已注册工作区'),
1810
+ React.createElement('input', {
1811
+ style: { ...s.input, width: '100%' },
1812
+ placeholder: '绝对路径,例如 /home/me/project 或 D:\\project',
1813
+ value: cfgDraft?.cwd ?? '',
1814
+ onChange: (e) => setCfgDraft(d => ({ ...d, cwd: e.target.value })),
1815
+ }),
1816
+ ),
1817
+ // 模型路由
1818
+ React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 8 } },
1819
+ React.createElement('div', null,
1820
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '模型提供方(可选)'),
1821
+ React.createElement('input', {
1822
+ style: { ...s.input, width: '100%' },
1823
+ placeholder: '留空 = DSH 默认',
1824
+ value: cfgDraft?.agentProvider ?? '',
1825
+ onChange: (e) => setCfgDraft(d => ({ ...d, agentProvider: e.target.value })),
1826
+ }),
1827
+ ),
1828
+ React.createElement('div', null,
1829
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '模型(可选)'),
1830
+ React.createElement('input', {
1831
+ style: { ...s.input, width: '100%' },
1832
+ placeholder: '留空 = DSH 默认',
1833
+ value: cfgDraft?.agentModel ?? '',
1834
+ onChange: (e) => setCfgDraft(d => ({ ...d, agentModel: e.target.value })),
1835
+ }),
1836
+ ),
1837
+ ),
1838
+ // 会话节奏参数
1839
+ React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 8 } },
1840
+ [
1841
+ ['digestIntervalSec', '进度摘要间隔(秒)'],
1842
+ ['approvalTimeoutSec', '审批超时(秒)'],
1843
+ ['maxMessageChars', '单条消息上限(字符)'],
1844
+ ['sendChunkDelayMs', '分块发送间隔(毫秒)'],
1845
+ ].map(([key, label]) =>
1846
+ React.createElement('div', { key },
1847
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, label),
1848
+ React.createElement('input', {
1849
+ style: { ...s.input, width: '100%' },
1850
+ inputMode: 'numeric',
1851
+ value: cfgDraft?.[key] ?? '',
1852
+ onChange: (e) => setCfgDraft(d => ({ ...d, [key]: e.target.value })),
1853
+ }),
1854
+ )),
1855
+ ),
1856
+ React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
1857
+ React.createElement('button', {
1858
+ style: { ...s.btnPri, opacity: (busy || !cfgDirty) ? 0.5 : 1 },
1859
+ onClick: saveConfig,
1860
+ disabled: busy || !cfgDirty,
1861
+ }, busy ? '保存中…' : '保存高级设置'),
1862
+ React.createElement('button', {
1863
+ style: { ...s.btnGhost },
1864
+ onClick: resetDefaults,
1865
+ disabled: busy,
1866
+ }, '恢复推荐节奏参数'),
1867
+ ),
1868
+ ),
1869
+ ),
1750
1870
  // 飞书 / Telegram 扫码直达对话引导卡片
1751
1871
  (platformId === 'feishu' || platformId === 'telegram') && platform.botQr && React.createElement('div', {
1752
1872
  style: {
@@ -90,4 +90,4 @@
90
90
  2. **安全白名单拦截**:
91
91
  - 仅白名单内的用户消息会被放行给 Agent;非白名单用户发送的消息会被直接忽略,绝不消耗 Token 或喂给模型。
92
92
  3. **敏感操作审批**:
93
- - 当 Agent 尝试执行系统命令或敏感文件读写时,Telegram 会自动下发 `[✓ 批准执行]` / `[✕ 拒绝执行]` 交互按键,10 分钟未处理自动超时拒绝。
93
+ - 当 Agent 尝试执行系统命令或敏感文件读写时,Telegram 会自动下发 `[✓ 批准执行]` / `[✕ 拒绝执行]` 交互按键,10 分钟未处理自动超时拒绝。
@@ -40,6 +40,8 @@ export const BRIDGE_ENDPOINTS = {
40
40
  platformStop: 'platformStop',
41
41
  platformStart: 'platformStart',
42
42
  platformUnbind: 'platformUnbind',
43
+ // 本机可用的 DSH agent preset(设置页下拉用;老版本 DSH 返回 available:false)
44
+ listAgentPresets: 'listAgentPresets',
43
45
  // 微信 Bot(v1.x 向后兼容别名,deprecated)
44
46
  wechatGetStatus: 'wechatGetStatus',
45
47
  wechatLogin: 'wechatLogin',
package/lib/bridge-rpc.js CHANGED
@@ -378,6 +378,29 @@ export function installBridgeRpc(ctx, { service, authManager, platformManager, l
378
378
 
379
379
  // ---- 平台管理器(多 IM 平台)----
380
380
 
381
+ // 本机可用的 DSH agent preset(设置页下拉用)。
382
+ // 只读、不含敏感信息:DSH 未提供 agentPresets 服务(旧版本)或名册读取失败时
383
+ // 返回 available:false,前端退化为手填,不影响会话创建。
384
+ if (endpoint === BRIDGE_ENDPOINTS.listAgentPresets) {
385
+ try {
386
+ const presets = ctx.get?.('agentPresets');
387
+ if (!presets?.list) return ok({ available: false, default: null, presets: [] });
388
+ const rows = await presets.list();
389
+ return ok({
390
+ available: true,
391
+ default: typeof presets.defaultId === 'string' ? presets.defaultId : null,
392
+ presets: (Array.isArray(rows) ? rows : [])
393
+ // 坏掉的预设不列出来(DSH 自己也会在挂载前拒绝)
394
+ .filter((p) => p && typeof p.id === 'string' && p.broken === undefined)
395
+ .map((p) => ({ id: p.id, name: typeof p.name === 'string' ? p.name : p.id })),
396
+ });
397
+ } catch (err) {
398
+ // 服务被卸载 / 名册不可读:这是设置页的辅助信息,不该让整个请求失败
399
+ logger?.warn?.('dsh-bridge: listAgentPresets failed: %s', err?.message ?? err);
400
+ return ok({ available: false, default: null, presets: [] });
401
+ }
402
+ }
403
+
381
404
  if (endpoint === BRIDGE_ENDPOINTS.listPlatforms) {
382
405
  if (!platformManager) return ok({});
383
406
  // 每个平台的 login.qrPayload 渲染为 dataURL 后返回