@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/lib/index.js CHANGED
@@ -18,6 +18,7 @@ import { installBridgeRpc } from './bridge-rpc.js';
18
18
  import { CustomTunnelClient } from './tunnel-client.mjs';
19
19
  import { CloudflaredManager } from './cloudflared-manager.mjs';
20
20
  import { PlatformManager } from './platform/manager.js';
21
+ import { applyRestoredPlatformConfig, RESTORED_STRING_FIELDS, PLATFORM_TIMING_FIELDS } from './platform/config-restore.js';
21
22
  import { WechatService } from './wechat/index.js';
22
23
  import { QqService } from './qq/index.js';
23
24
  import { FeishuService } from './feishu/index.js';
@@ -1954,15 +1955,13 @@ function apply(ctx, config = {}) {
1954
1955
  const cfg = stored?.[platformKey];
1955
1956
  if (!cfg) return;
1956
1957
  const node = service.node;
1957
- node.config.allowFrom = Array.isArray(cfg.allowFrom) ? cfg.allowFrom : [];
1958
- for (const field of numericFields) {
1959
- if (cfg[field] != null) node.config[field] = Number(cfg[field]);
1960
- }
1961
- if (cfg.maxMessageChars != null) {
1962
- const val = Number(cfg.maxMessageChars);
1963
- node.config.maxMessageChars = (val >= 200) ? val : defaultMaxMessageChars;
1964
- }
1965
- if (cfg.groupAutoApprove != null) node.config.groupAutoApprove = cfg.groupAutoApprove === true;
1958
+ // 白名单/数值字段 + 会话字符串字段(cwd/agentPreset/agentProvider/agentModel)一并回写,
1959
+ // 否则"设置里配了、重启就丢",远程会话会掉到默认或空 preset 层
1960
+ applyRestoredPlatformConfig(node.config, cfg, {
1961
+ numericFields,
1962
+ stringFields: RESTORED_STRING_FIELDS,
1963
+ defaultMaxMessageChars,
1964
+ });
1966
1965
 
1967
1966
  node._restoringConfig = (async () => {
1968
1967
  if (cfg.activeSessionId) {
@@ -1987,7 +1986,7 @@ function apply(ctx, config = {}) {
1987
1986
 
1988
1987
  restorePlatform(wechat, {
1989
1988
  platformKey: 'wechat',
1990
- numericFields: ['digestIntervalSec', 'approvalTimeoutSec', 'sendChunkDelayMs'],
1989
+ numericFields: PLATFORM_TIMING_FIELDS,
1991
1990
  hasCredentials: (cfg) => Boolean(cfg.token && cfg.accountId),
1992
1991
  applyCredentials: (cfg) => wechat.gateway.setCredentials({
1993
1992
  token: cfg.token,
@@ -1998,6 +1997,7 @@ function apply(ctx, config = {}) {
1998
1997
 
1999
1998
  restorePlatform(qq, {
2000
1999
  platformKey: 'qq',
2000
+ numericFields: PLATFORM_TIMING_FIELDS,
2001
2001
  hasCredentials: (cfg) => Boolean(cfg.appId && cfg.clientSecret),
2002
2002
  applyCredentials: (cfg) => qq.gateway.setCredentials({
2003
2003
  appId: cfg.appId,
@@ -2011,6 +2011,7 @@ function apply(ctx, config = {}) {
2011
2011
 
2012
2012
  restorePlatform(feishu, {
2013
2013
  platformKey: 'feishu',
2014
+ numericFields: PLATFORM_TIMING_FIELDS,
2014
2015
  hasCredentials: (cfg) => Boolean(cfg.appId && cfg.appSecret),
2015
2016
  applyCredentials: (cfg) => feishu.gateway.updateConfig({
2016
2017
  appId: cfg.appId,
@@ -2021,7 +2022,7 @@ function apply(ctx, config = {}) {
2021
2022
 
2022
2023
  restorePlatform(telegram, {
2023
2024
  platformKey: 'telegram',
2024
- numericFields: ['digestIntervalSec', 'approvalTimeoutSec', 'sendChunkDelayMs'],
2025
+ numericFields: PLATFORM_TIMING_FIELDS,
2025
2026
  defaultMaxMessageChars: 4096,
2026
2027
  hasCredentials: (cfg) => Boolean(cfg.botToken),
2027
2028
  applyCredentials: (cfg) => telegram.gateway.setCredentials({
@@ -2086,22 +2087,24 @@ function apply(ctx, config = {}) {
2086
2087
  if (incoming.customTunnel) {
2087
2088
  service.customTunnelConfig = incoming.customTunnel;
2088
2089
  }
2089
- // 重新载入各 IM 平台白名单与配置
2090
- if (incoming.wechat) wechat.node.config.allowFrom = incoming.wechat.allowFrom ?? [];
2090
+ // 重新载入各 IM 平台白名单与配置(含 cwd/agentPreset/agentProvider/agentModel)
2091
+ if (incoming.wechat) {
2092
+ applyRestoredPlatformConfig(wechat.node.config, incoming.wechat, { stringFields: RESTORED_STRING_FIELDS });
2093
+ }
2091
2094
  if (incoming.qq) {
2092
- qq.node.config.allowFrom = incoming.qq.allowFrom ?? [];
2095
+ applyRestoredPlatformConfig(qq.node.config, incoming.qq, { stringFields: RESTORED_STRING_FIELDS });
2093
2096
  if (incoming.qq.appId && incoming.qq.clientSecret) {
2094
2097
  qq.gateway.setCredentials({ appId: incoming.qq.appId, clientSecret: incoming.qq.clientSecret });
2095
2098
  }
2096
2099
  }
2097
2100
  if (incoming.feishu) {
2098
- feishu.node.config.allowFrom = incoming.feishu.allowFrom ?? [];
2101
+ applyRestoredPlatformConfig(feishu.node.config, incoming.feishu, { stringFields: RESTORED_STRING_FIELDS });
2099
2102
  if (incoming.feishu.appId && incoming.feishu.appSecret) {
2100
2103
  feishu.gateway.setCredentials({ appId: incoming.feishu.appId, appSecret: incoming.feishu.appSecret });
2101
2104
  }
2102
2105
  }
2103
2106
  if (incoming.telegram) {
2104
- telegram.node.config.allowFrom = incoming.telegram.allowFrom ?? [];
2107
+ applyRestoredPlatformConfig(telegram.node.config, incoming.telegram, { stringFields: RESTORED_STRING_FIELDS });
2105
2108
  if (incoming.telegram.botToken) {
2106
2109
  telegram.gateway.setCredentials({ botToken: incoming.telegram.botToken, proxy: incoming.telegram.proxy || '' });
2107
2110
  }
@@ -1,147 +1,147 @@
1
- // dsh-bridge 平台抽象基类
2
- //
3
- // 定义 IM 平台适配器的统一接口。每个平台(微信/QQ/飞书/Telegram…)继承本类,
4
- // 实现协议层(登录、收发消息、typing)。平台无关的会话桥逻辑在 ConversationBridge
5
- // (lib/platform/conversation-bridge.js)中实现,通过 platform 注入到 bridge。
6
- //
7
- // 生命周期:constructor → start() → stop() → dispose()
8
- // 消息抽象:sendText / sendTyping / sendMedia(由子类实现)
9
-
10
- export class Platform {
11
- /**
12
- * @param {object} opts
13
- * @param {object} opts.ctx Cordis 上下文
14
- * @param {object} opts.logger 日志器
15
- * @param {object} [opts.config] 已持久化的平台配置(凭证等)
16
- * @param {(patch: object) => (void|Promise<void>)} [opts.onPersist] 主插件保存回调
17
- * @param {import('./conversation-bridge.js').ConversationBridge} [opts.bridge] 会话桥实例
18
- */
19
- constructor({ ctx, logger, config = {}, onPersist, bridge } = {}) {
20
- this.ctx = ctx
21
- this.logger = logger
22
- this.config = { ...config }
23
- this.onPersist = onPersist ?? (() => {})
24
- this.bridge = bridge ?? null
25
-
26
- // 平台标识(子类必须设置)
27
- this.id = ''
28
- this.name = ''
29
-
30
- // 连接状态与账号:子类可能用 getter 覆盖(如委托给 gateway),
31
- // 因此仅在未被子类覆盖时才初始化默认值。
32
- if (!('status' in this)) this.status = 'idle'
33
- if (!('accountId' in this)) this.accountId = null
34
-
35
- // 扫码/登录的流式状态(RPC 轮询读取)
36
- this.loginState = {
37
- phase: 'idle', // idle | qr | scaned | confirmed | done | error
38
- qrPayload: null, // 待渲染内容:dataURL 图片 或 二维码文本
39
- qrKind: null, // 'img' | 'text'
40
- error: null,
41
- }
42
-
43
- this.disposers = []
44
- }
45
-
46
- // ---- 平台能力声明(子类可覆盖)----
47
-
48
- get capabilities() {
49
- return {
50
- supportsGroup: false, // 是否支持群聊
51
- supportsMedia: false, // 是否支持媒体收发
52
- supportsVoice: false, // 是否支持语音
53
- supportsTyping: false, // 是否支持 typing 状态
54
- maxMessageChars: 2000, // 单条消息最大字符数
55
- }
56
- }
57
-
58
- get configured() {
59
- return false
60
- }
61
-
62
- // ---- 生命周期(子类必须实现 start/stop;dispose 已提供默认实现)----
63
-
64
- async start() {
65
- throw new Error(`${this.id || 'platform'}: start() not implemented`)
66
- }
67
-
68
- async stop() {
69
- throw new Error(`${this.id || 'platform'}: stop() not implemented`)
70
- }
71
-
72
- dispose() {
73
- for (const disposer of this.disposers) {
74
- try { disposer() } catch { /* 忽略 */ }
75
- }
76
- this.disposers = []
77
- this.bridge?.dispose?.()
78
- this.bridge = null
79
- }
80
-
81
- // ---- 消息抽象(子类必须实现)----
82
-
83
- async sendText(peerId, text, opts = {}) {
84
- throw new Error(`${this.id || 'platform'}: sendText() not implemented`)
85
- }
86
-
87
- async sendTyping(peerId, state) {
88
- return Promise.resolve()
89
- }
90
-
91
- async sendMedia(peerId, media, opts = {}) {
92
- throw new Error(`${this.id || 'platform'}: sendMedia() not implemented`)
93
- }
94
-
95
- // ---- 登录(子类必须实现 login;getLoginState 已提供默认)----
96
-
97
- async login(opts = {}) {
98
- throw new Error(`${this.id || 'platform'}: login() not implemented`)
99
- }
100
-
101
- getLoginState() {
102
- return { ...this.loginState }
103
- }
104
-
105
- // ---- 状态汇总(供 RPC/UI 读取)----
106
-
107
- getStatus() {
108
- return {
109
- id: this.id,
110
- name: this.name,
111
- status: this.status,
112
- configured: this.configured,
113
- accountId: this.accountId,
114
- login: this.getLoginState(),
115
- peerId: this.bridge?.peerId ?? null,
116
- sessionId: this.bridge?.activeSessionId ?? null,
117
- config: this.getEditableConfig?.(),
118
- }
119
- }
120
-
121
- /** 可编辑配置(供 UI 设置面板读取);子类可覆盖返回具体字段。 */
122
- getEditableConfig() {
123
- return {}
124
- }
125
-
126
- // ---- 工具 ----
127
-
128
- setStatus(status) {
129
- if (this.status === status) return
130
- this.status = status
131
- try {
132
- this.ctx.emit?.(`${this.id}/status`, status)
133
- } catch { /* emit 失败不致命 */ }
134
- }
135
-
136
- async persist(patch) {
137
- try {
138
- await this.onPersist(patch)
139
- } catch (err) {
140
- this.logger?.warn?.(`[dsh-bridge ${this.id}] persist failed: %s`, err?.message ?? err)
141
- }
142
- }
143
-
144
- async destroy() {
145
- this.dispose()
146
- }
147
- }
1
+ // dsh-bridge 平台抽象基类
2
+ //
3
+ // 定义 IM 平台适配器的统一接口。每个平台(微信/QQ/飞书/Telegram…)继承本类,
4
+ // 实现协议层(登录、收发消息、typing)。平台无关的会话桥逻辑在 ConversationBridge
5
+ // (lib/platform/conversation-bridge.js)中实现,通过 platform 注入到 bridge。
6
+ //
7
+ // 生命周期:constructor → start() → stop() → dispose()
8
+ // 消息抽象:sendText / sendTyping / sendMedia(由子类实现)
9
+
10
+ export class Platform {
11
+ /**
12
+ * @param {object} opts
13
+ * @param {object} opts.ctx Cordis 上下文
14
+ * @param {object} opts.logger 日志器
15
+ * @param {object} [opts.config] 已持久化的平台配置(凭证等)
16
+ * @param {(patch: object) => (void|Promise<void>)} [opts.onPersist] 主插件保存回调
17
+ * @param {import('./conversation-bridge.js').ConversationBridge} [opts.bridge] 会话桥实例
18
+ */
19
+ constructor({ ctx, logger, config = {}, onPersist, bridge } = {}) {
20
+ this.ctx = ctx
21
+ this.logger = logger
22
+ this.config = { ...config }
23
+ this.onPersist = onPersist ?? (() => {})
24
+ this.bridge = bridge ?? null
25
+
26
+ // 平台标识(子类必须设置)
27
+ this.id = ''
28
+ this.name = ''
29
+
30
+ // 连接状态与账号:子类可能用 getter 覆盖(如委托给 gateway),
31
+ // 因此仅在未被子类覆盖时才初始化默认值。
32
+ if (!('status' in this)) this.status = 'idle'
33
+ if (!('accountId' in this)) this.accountId = null
34
+
35
+ // 扫码/登录的流式状态(RPC 轮询读取)
36
+ this.loginState = {
37
+ phase: 'idle', // idle | qr | scaned | confirmed | done | error
38
+ qrPayload: null, // 待渲染内容:dataURL 图片 或 二维码文本
39
+ qrKind: null, // 'img' | 'text'
40
+ error: null,
41
+ }
42
+
43
+ this.disposers = []
44
+ }
45
+
46
+ // ---- 平台能力声明(子类可覆盖)----
47
+
48
+ get capabilities() {
49
+ return {
50
+ supportsGroup: false, // 是否支持群聊
51
+ supportsMedia: false, // 是否支持媒体收发
52
+ supportsVoice: false, // 是否支持语音
53
+ supportsTyping: false, // 是否支持 typing 状态
54
+ maxMessageChars: 2000, // 单条消息最大字符数
55
+ }
56
+ }
57
+
58
+ get configured() {
59
+ return false
60
+ }
61
+
62
+ // ---- 生命周期(子类必须实现 start/stop;dispose 已提供默认实现)----
63
+
64
+ async start() {
65
+ throw new Error(`${this.id || 'platform'}: start() not implemented`)
66
+ }
67
+
68
+ async stop() {
69
+ throw new Error(`${this.id || 'platform'}: stop() not implemented`)
70
+ }
71
+
72
+ dispose() {
73
+ for (const disposer of this.disposers) {
74
+ try { disposer() } catch { /* 忽略 */ }
75
+ }
76
+ this.disposers = []
77
+ this.bridge?.dispose?.()
78
+ this.bridge = null
79
+ }
80
+
81
+ // ---- 消息抽象(子类必须实现)----
82
+
83
+ async sendText(peerId, text, opts = {}) {
84
+ throw new Error(`${this.id || 'platform'}: sendText() not implemented`)
85
+ }
86
+
87
+ async sendTyping(peerId, state) {
88
+ return Promise.resolve()
89
+ }
90
+
91
+ async sendMedia(peerId, media, opts = {}) {
92
+ throw new Error(`${this.id || 'platform'}: sendMedia() not implemented`)
93
+ }
94
+
95
+ // ---- 登录(子类必须实现 login;getLoginState 已提供默认)----
96
+
97
+ async login(opts = {}) {
98
+ throw new Error(`${this.id || 'platform'}: login() not implemented`)
99
+ }
100
+
101
+ getLoginState() {
102
+ return { ...this.loginState }
103
+ }
104
+
105
+ // ---- 状态汇总(供 RPC/UI 读取)----
106
+
107
+ getStatus() {
108
+ return {
109
+ id: this.id,
110
+ name: this.name,
111
+ status: this.status,
112
+ configured: this.configured,
113
+ accountId: this.accountId,
114
+ login: this.getLoginState(),
115
+ peerId: this.bridge?.peerId ?? null,
116
+ sessionId: this.bridge?.activeSessionId ?? null,
117
+ config: this.getEditableConfig?.(),
118
+ }
119
+ }
120
+
121
+ /** 可编辑配置(供 UI 设置面板读取);子类可覆盖返回具体字段。 */
122
+ getEditableConfig() {
123
+ return {}
124
+ }
125
+
126
+ // ---- 工具 ----
127
+
128
+ setStatus(status) {
129
+ if (this.status === status) return
130
+ this.status = status
131
+ try {
132
+ this.ctx.emit?.(`${this.id}/status`, status)
133
+ } catch { /* emit 失败不致命 */ }
134
+ }
135
+
136
+ async persist(patch) {
137
+ try {
138
+ await this.onPersist(patch)
139
+ } catch (err) {
140
+ this.logger?.warn?.(`[dsh-bridge ${this.id}] persist failed: %s`, err?.message ?? err)
141
+ }
142
+ }
143
+
144
+ async destroy() {
145
+ this.dispose()
146
+ }
147
+ }