@wenbin_wb/dsh-bridge 2.5.3 → 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 +2 -2
- package/README.md +2 -2
- package/lib/feishu/node.js +12 -0
- package/lib/index.js +6 -4
- package/lib/platform/conversation-bridge.js +38 -5
- package/lib/qq/node.js +7 -0
- package/lib/telegram/node.js +8 -0
- package/package.json +9 -3
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
|
|
107
|
-
dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.
|
|
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.
|
|
107
|
-
dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.
|
|
106
|
+
# 或指定版本(如 2.5.5)
|
|
107
|
+
dsh plugin --profile web add @wenbin_wb/dsh-bridge@2.5.5
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
> 💡 **没有全局安装权限?** 使用 `npx` 方式:
|
package/lib/feishu/node.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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
|
// 导出,便于测试与复用
|
package/lib/telegram/node.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "2.5.5",
|
|
4
4
|
"description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -28,17 +28,23 @@
|
|
|
28
28
|
"test": "node --test test/*.test.mjs"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
32
31
|
"qrcode": "^1.5.3",
|
|
33
32
|
"ws": "^8.18.0"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
36
36
|
"@larksuiteoapi/node-sdk": "^1.73.0",
|
|
37
37
|
"esbuild": "^0.25.9",
|
|
38
38
|
"puppeteer-core": "^25.8.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
42
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@deepseek-ai/dsh-llm": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
42
48
|
},
|
|
43
49
|
"engines": {
|
|
44
50
|
"node": "^22.19.0 || >=24.0.0"
|