@wenbin_wb/dsh-bridge 2.5.4 → 2.5.5

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
@@ -103,8 +103,8 @@ npm install -g @deepseek-ai/dsh
103
103
  # Install the latest version
104
104
  dsh plugin --profile web add @wenbin_wb/dsh-bridge
105
105
 
106
- # Or install a specific version (e.g. 2.5.4)
107
- dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.4
106
+ # Or install a specific version (e.g. 2.5.5)
107
+ dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.5
108
108
  ```
109
109
 
110
110
  > 💡 **No global install permission?** Use `npx`:
package/README.md CHANGED
@@ -103,8 +103,8 @@ npm install -g @deepseek-ai/dsh
103
103
  # 安装最新版
104
104
  dsh plugin --profile web add @wenbin_wb/dsh-bridge
105
105
 
106
- # 或指定版本(如 2.5.4
107
- dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.4
106
+ # 或指定版本(如 2.5.5
107
+ dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.5
108
108
  ```
109
109
 
110
110
  > 💡 **没有全局安装权限?** 使用 `npx` 方式:
@@ -389,6 +389,18 @@ export class FeishuConversationNode extends ConversationBridge {
389
389
  const disposer = this.ctx.on('approval/request', listener)
390
390
  this.disposers.push(disposer)
391
391
  }
392
+
393
+ dispose() {
394
+ if (this._patchTimer) {
395
+ clearTimeout(this._patchTimer)
396
+ this._patchTimer = null
397
+ }
398
+ this._inTurn = false
399
+ this._streamCardId = null
400
+ this._streamContent = ''
401
+ this._lastPeer = null
402
+ super.dispose()
403
+ }
392
404
  }
393
405
 
394
406
  export const feishuNodeHelpers = {
package/lib/index.js CHANGED
@@ -148,8 +148,10 @@ class ProxyServer {
148
148
  if (this.server) return;
149
149
 
150
150
  this.server = createServer((req, res) => {
151
+ const pathname = (req.url || '/').split('?')[0].replace(/\/+$/, '') || '/';
152
+
151
153
  // 1. 处理登录 API: POST /__dsh_bridge__/login
152
- if (req.url === '/__dsh_bridge__/login' && req.method === 'POST') {
154
+ if (pathname === '/__dsh_bridge__/login' && req.method === 'POST') {
153
155
  const chunks = [];
154
156
  req.on('data', (c) => chunks.push(c));
155
157
  req.on('end', () => {
@@ -177,7 +179,7 @@ class ProxyServer {
177
179
  }
178
180
 
179
181
  // 2. 处理登出 API: POST /__dsh_bridge__/logout
180
- if (req.url === '/__dsh_bridge__/logout' && req.method === 'POST') {
182
+ if (pathname === '/__dsh_bridge__/logout' && req.method === 'POST') {
181
183
  res.writeHead(200, {
182
184
  'Content-Type': 'application/json; charset=utf-8',
183
185
  'Set-Cookie': 'dsh_bridge_auth=; Path=/; HttpOnly; Max-Age=0',
@@ -187,14 +189,14 @@ class ProxyServer {
187
189
  }
188
190
 
189
191
  // 3. 处理鉴权状态 API: GET /__dsh_bridge__/auth-status (严格脱敏,不暴露 secretToken)
190
- if (req.url === '/__dsh_bridge__/auth-status' && req.method === 'GET') {
192
+ if (pathname === '/__dsh_bridge__/auth-status' && req.method === 'GET') {
191
193
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
192
194
  res.end(JSON.stringify(this.authManager?.getPublicStatus() ?? { enabled: false }));
193
195
  return;
194
196
  }
195
197
 
196
198
  // 3.1 本机特权 Token 签发:仅限真正物理回环连接(127.0.0.1 / ::1,严禁隧道转发流量伪造)
197
- if (req.url === '/__dsh_bridge__/loopback-token') {
199
+ if (pathname === '/__dsh_bridge__/loopback-token') {
198
200
  const corsHeaders = {
199
201
  'Access-Control-Allow-Origin': '*',
200
202
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
@@ -175,6 +175,7 @@ function digestLine(session) {
175
175
  lastTool = event.data.name
176
176
  }
177
177
  }
178
+ if (!inTurn || turn === 0) return null
178
179
  const steps = tools > 0 ? `${tools} 次工具调用` : '思考中'
179
180
  const last = lastTool ? ` | 最近: ${lastTool}` : ''
180
181
  return `[处理中] 第 ${turn} 轮 | ${steps}${last}`
@@ -271,6 +272,7 @@ export class ConversationBridge {
271
272
  }
272
273
 
273
274
  setActiveSession(session) {
275
+ this.stopAllHeartbeats()
274
276
  this.activeSessionId = session.id
275
277
  try { this.onActiveSessionChange?.(session.id) } catch { /* 持久化失败不致命 */ }
276
278
  }
@@ -279,6 +281,7 @@ export class ConversationBridge {
279
281
  // 发消息时通过 re-attach 逻辑拉起 agent。
280
282
  setActiveSessionById(id) {
281
283
  if (!id) return
284
+ this.stopAllHeartbeats()
282
285
  this.activeSessionId = id
283
286
  try { this.onActiveSessionChange?.(id) } catch { /* 持久化失败不致命 */ }
284
287
  }
@@ -388,7 +391,19 @@ export class ConversationBridge {
388
391
  return false
389
392
  }
390
393
 
394
+ stopAllHeartbeats() {
395
+ if (this._digestState) {
396
+ for (const state of this._digestState.values()) {
397
+ if (state && state.heartbeat) {
398
+ clearInterval(state.heartbeat)
399
+ state.heartbeat = undefined
400
+ }
401
+ }
402
+ }
403
+ }
404
+
391
405
  dispose() {
406
+ this.stopAllHeartbeats()
392
407
  for (const disposer of this.disposers) {
393
408
  try { disposer() } catch { /* 忽略 */ }
394
409
  }
@@ -563,9 +578,9 @@ export class ConversationBridge {
563
578
  // ---- 出站事件绑定 ----
564
579
 
565
580
  _attachOutbound() {
566
- const digestState = new Map()
581
+ this._digestState = new Map()
567
582
  const stopHeartbeat = (state) => {
568
- if (state.heartbeat) {
583
+ if (state && state.heartbeat) {
569
584
  clearInterval(state.heartbeat)
570
585
  state.heartbeat = undefined
571
586
  }
@@ -574,16 +589,32 @@ export class ConversationBridge {
574
589
  stopHeartbeat(state)
575
590
  if (this.config.digestIntervalSec <= 0) return
576
591
  state.heartbeat = setInterval(() => {
592
+ // 1. 必须依然是当前活动会话
593
+ if (this.activeSessionId !== session.id) {
594
+ stopHeartbeat(state)
595
+ return
596
+ }
597
+ // 2. 检查会话当前是否真正处于 inTurn 状态中
598
+ const line = digestLine(session)
599
+ if (!line) {
600
+ stopHeartbeat(state)
601
+ return
602
+ }
577
603
  // 心跳时同时刷新 typing 状态(微信 typing 只维持 15 秒)
578
604
  if (this.peerId) this.sendTyping(1).catch(() => {})
579
- void this.sendText(digestLine(session))
605
+ void this.sendText(line)
580
606
  }, this.config.digestIntervalSec * 1000)
581
607
  if (typeof state.heartbeat.unref === 'function') state.heartbeat.unref()
582
608
  }
583
609
  const onEvent = (session, event) => {
610
+ const state = this._digestState.get(session.id) ?? { startedTurns: new Set(), createdFiles: new Set() }
611
+ this._digestState.set(session.id, state)
612
+
613
+ // 无论是否为当前活动会话,一旦收到 turn/end,立即停止该 session 的心跳定时器
614
+ if (event.type === 'turn/end') {
615
+ stopHeartbeat(state)
616
+ }
584
617
  if (session.id !== this.activeSessionId) return
585
- const state = digestState.get(session.id) ?? { startedTurns: new Set(), createdFiles: new Set() }
586
- digestState.set(session.id, state)
587
618
 
588
619
  if (event.type === 'turn/start') {
589
620
  const turn = event.data?.turn
@@ -930,6 +961,7 @@ async function routeCommand(node, text) {
930
961
  return true
931
962
  }
932
963
  case 'stop': {
964
+ node.stopAllHeartbeats()
933
965
  const agent = node.activeAgent()
934
966
  if (!agent) {
935
967
  await node.sendText(`ℹ️ **当前没有正在运行的 Agent 任务**`)
@@ -940,6 +972,7 @@ async function routeCommand(node, text) {
940
972
  return true
941
973
  }
942
974
  case 'end': {
975
+ node.stopAllHeartbeats()
943
976
  // 结束当前会话:停止 agent 并清除活动会话(进入"没有活动会话"状态)
944
977
  const agent = node.activeAgent()
945
978
  if (agent) agent.cancel({ kind: 'user' })
package/lib/qq/node.js CHANGED
@@ -508,6 +508,13 @@ export class QqConversationNode extends ConversationBridge {
508
508
  this.logger?.info?.(`[dsh-bridge qq] unknown button interaction: ${buttonId}`)
509
509
  }
510
510
  }
511
+
512
+ dispose() {
513
+ this._lastPeer = null
514
+ this._replyMsgId = null
515
+ this.lastMessageId = null
516
+ super.dispose()
517
+ }
511
518
  }
512
519
 
513
520
  // 导出,便于测试与复用
@@ -337,6 +337,14 @@ export class TelegramConversationNode extends ConversationBridge {
337
337
  if (!peerId || !this.gateway) return
338
338
  return this.gateway.sendTyping(peerId)
339
339
  }
340
+
341
+ dispose() {
342
+ this._inTurn = false
343
+ this._streamMsgId = null
344
+ this._streamContent = ''
345
+ this._lastPeer = null
346
+ super.dispose()
347
+ }
340
348
  }
341
349
 
342
350
  export const telegramNodeHelpers = { makePlatform }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.5.4",
3
+ "version": "2.5.5",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",