@zaofan/dsh-qqbot 1.5.0 → 1.5.3
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.md +1 -1
- package/README_EN.md +7 -7
- package/client/qqbot-settings.js +52 -8
- package/dist/channel-tools.d.ts.map +1 -1
- package/dist/channel-tools.js +43 -1
- package/dist/channel-tools.js.map +1 -1
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/features/agg-score.d.ts +1 -1
- package/dist/features/agg-score.js +1 -1
- package/dist/features/four-source.d.ts +46 -10
- package/dist/features/four-source.d.ts.map +1 -1
- package/dist/features/four-source.js +94 -17
- package/dist/features/four-source.js.map +1 -1
- package/dist/features/local-signals.js +2 -2
- package/dist/features/local-signals.js.map +1 -1
- package/dist/features/people-memo.js +1 -1
- package/dist/features/tendency-samples.d.ts.map +1 -1
- package/dist/features/tendency-samples.js +36 -0
- package/dist/features/tendency-samples.js.map +1 -1
- package/dist/features/value-sample.d.ts +3 -1
- package/dist/features/value-sample.d.ts.map +1 -1
- package/dist/features/value-sample.js +3 -1
- package/dist/features/value-sample.js.map +1 -1
- package/dist/features/value-score.d.ts +27 -0
- package/dist/features/value-score.d.ts.map +1 -1
- package/dist/features/value-score.js +72 -4
- package/dist/features/value-score.js.map +1 -1
- package/dist/gateway/debounce.d.ts.map +1 -1
- package/dist/gateway/debounce.js +28 -6
- package/dist/gateway/debounce.js.map +1 -1
- package/dist/shared/mention-clean.d.ts +13 -0
- package/dist/shared/mention-clean.d.ts.map +1 -1
- package/dist/shared/mention-clean.js +19 -0
- package/dist/shared/mention-clean.js.map +1 -1
- package/dist/transport/inbound.d.ts +10 -0
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +129 -51
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/markers.d.ts +59 -0
- package/dist/transport/markers.d.ts.map +1 -0
- package/dist/transport/markers.js +97 -0
- package/dist/transport/markers.js.map +1 -0
- package/dist/transport/outbound.d.ts.map +1 -1
- package/dist/transport/outbound.js +3 -1
- package/dist/transport/outbound.js.map +1 -1
- package/docs/USER-GUIDE.md +461 -429
- package/package.json +124 -124
- package/settings-host.js +3095 -3071
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
**🧑🤝🧑 一个电脑,N 条鲸鱼同时在线**
|
|
24
24
|
每条号独立 AppID、独立人格、独立图库/定时/闸门,互不串号;Web 页「扫码绑定」手机一扫就上岗。
|
|
25
25
|
|
|
26
|
-
**💗 她记得住谁亲谁疏**(v1.5.
|
|
26
|
+
**💗 她记得住谁亲谁疏**(v1.5.1)
|
|
27
27
|
两个维度分开算:**熟识度**(她把你记得多牢——来过几天、说过多少、被点名、接话,慢变量)与**好感度**(她对你什么态度——随每次互动可升可降)。面板里一行一个人,还能**一键导出 Excel** 拿去分享。
|
|
28
28
|
- 判定用一个跑在本机的小模型读她的「思考(内心)」与「正文(说出口的话)」:**心里不肯但话仍照顾 = 让步**、**心里亲近而嘴上冷淡 = 不算数**(扣不扣看内心,不看表面)
|
|
29
29
|
- 好感度只影响"她愿不愿意自己开口"的松紧:**负好感也只是少主动,绝不冷落、阴阳、攻击**
|
package/README_EN.md
CHANGED
|
@@ -4,8 +4,8 @@ An **enhanced fork** of the QQ Bot IM plugin for [deepseek-harness](https://gith
|
|
|
4
4
|
|
|
5
5
|
📦 Repo: [gcry13067381632-jpg/dsh-qqbot](https://github.com/gcry13067381632-jpg/dsh-qqbot) (forked from [tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot))
|
|
6
6
|
|
|
7
|
-
> ⭐ **If it works well for you, please star the repo** — it is the only visible signal that someone is using it, and it keeps the project going.
|
|
8
|
-
|
|
7
|
+
> ⭐ **If it works well for you, please star the repo** — it is the only visible signal that someone is using it, and it keeps the project going.
|
|
8
|
+
|
|
9
9
|
[中文文档](./README.md) | English
|
|
10
10
|
|
|
11
11
|
## 🐋 This fork (enhanced edition)
|
|
@@ -290,7 +290,7 @@ Please install the local small model used by the dsh qqbot plugin on this machin
|
|
|
290
290
|
Every inbound message carries a **short message number** (e.g. `#0913a` — month/day + rolling id):
|
|
291
291
|
|
|
292
292
|
```
|
|
293
|
-
[
|
|
293
|
+
[Alice (12AB34…) #0913a] take a look at this
|
|
294
294
|
```
|
|
295
295
|
|
|
296
296
|
- To quote a message, the AI writes `[rf:0913a]` in its reply body → the reply is sent as a **quote bubble** (the other side sees "she quoted this message"). The tag itself is never displayed.
|
|
@@ -410,10 +410,10 @@ export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
|
|
|
410
410
|
npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
|
|
411
411
|
```
|
|
412
412
|
|
|
413
|
-
## Support
|
|
414
|
-
|
|
415
|
-
If this plugin saved you some tokens — or made your QQ bot feel alive — a **⭐ Star** is the most concrete way to say thanks. Bugs and feature requests: [Issues](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues).
|
|
416
|
-
|
|
413
|
+
## Support
|
|
414
|
+
|
|
415
|
+
If this plugin saved you some tokens — or made your QQ bot feel alive — a **⭐ Star** is the most concrete way to say thanks. Bugs and feature requests: [Issues](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues).
|
|
416
|
+
|
|
417
417
|
## License
|
|
418
418
|
|
|
419
419
|
[MIT](./LICENSE)
|
package/client/qqbot-settings.js
CHANGED
|
@@ -2008,7 +2008,13 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2008
2008
|
var col = it.worth ? '#2f9e44' : '#c23131'
|
|
2009
2009
|
out += '<div style="padding:4px 8px;border-bottom:1px solid #f5f5f5;font-size:12px">'
|
|
2010
2010
|
+ '<span style="color:#999">' + hh + '</span> '
|
|
2011
|
-
|
|
2011
|
+
// 显示"判定真正用的那个分":优先 scoreAdj(好感偏移 / 加权综合后的结果),没有才退回原始分;
|
|
2012
|
+
// 两者都没有(纯图、且窗口里也没人说过话)才显示 📷
|
|
2013
|
+
// —— 2026-09-14 修:原来只看 it.score,导致"📷"和"[合·加权]"同时出现
|
|
2014
|
+
+ '<b style="color:' + col + '">' + (function () {
|
|
2015
|
+
var shown = typeof it.scoreAdj === 'number' ? it.scoreAdj : (typeof it.score === 'number' ? it.score : undefined)
|
|
2016
|
+
return shown === undefined ? (it.img ? '📷' : '-') : Number(shown).toFixed(2)
|
|
2017
|
+
})() + '</b>'
|
|
2012
2018
|
// 好感偏移标记(2026-09-14:**偏移加在分数上,门槛不动**)—— 文案给普通用户看,写成"基础评分+好感偏移"
|
|
2013
2019
|
+ (it.aggWeighted && Array.isArray(it.aggParts) && it.aggParts.length
|
|
2014
2020
|
// 聚合加权(2026-09-14 主人定):判定分是**加权平均**,这里把每一票摊开给人看,
|
|
@@ -2030,10 +2036,17 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2030
2036
|
+ (typeof it.min === 'number'
|
|
2031
2037
|
? ' <span style="color:#888" title="' + esc('基础评分门槛 ' + Number(it.min).toFixed(2) + '(你设置的判定线);好感度以「好感偏移」加减在消息分数上(亲近加分、冷淡减分),不改动门槛') + '">门槛' + Number(it.min).toFixed(2) + '</span>'
|
|
2032
2038
|
: '')
|
|
2033
|
-
+ (it.img ? ' <span style="color:#999" title="
|
|
2039
|
+
+ (it.img ? ' <span style="color:#999" title="' + esc(it.lib
|
|
2040
|
+
? (it.libText
|
|
2041
|
+
? '图片消息:已在库 → 借它的标签/描述当文字评分'
|
|
2042
|
+
: '图片消息:在库里但还没打标签(待整理区)→ 借不到文字,所以没有分数(图片消息默认不拦)')
|
|
2043
|
+
: '图片消息:无文字,不可评分(默认不拦)') + '">'
|
|
2044
|
+
+ (it.lib ? (it.libText ? '[库内]' : '[库内·未打标]') : '[无文字]') + '</span>' : '')
|
|
2034
2045
|
+ (it.mention ? ' <span style="color:#1c7ed6" title="' + esc(it.mentionForced && !it.worth ? '被@ → 必回(分数没到门槛也回)' : '被@, 必回') + '">[@]</span>' : '')
|
|
2035
2046
|
+ (it.conf !== undefined && it.conf < 0.5 ? ' <span style="color:#999" title="低置信: 最近邻居相似度只有 ' + it.conf + ', 地图上没这类样本 —— 仅作补样本提示, 不再影响拦截">[?]</span>' : '')
|
|
2036
|
-
|
|
2047
|
+
// 聚合标记:加权模式左边已经有 [合N·加权] 了,这里**不再重复**一个 [合N]
|
|
2048
|
+
// (2026-09-14 主人截图里两个并排,看着像两条记录)
|
|
2049
|
+
+ (it.agg && !it.aggWeighted ? ' <span style="color:#e8590c" title="聚合了 ' + it.agg + ' 条消息, 取其中最高分">[合' + it.agg + ']</span>' : '')
|
|
2037
2050
|
+ ' <span style="color:#555">' + esc(String(it.sender || '?')) + ':</span> '
|
|
2038
2051
|
+ esc(String(it.text || '').slice(0, 56))
|
|
2039
2052
|
+ '<div style="color:#bbb;font-size:11px;margin-left:14px">近邻: ' + esc((it.top || []).join(' · ')) + '</div>'
|
|
@@ -2112,7 +2125,9 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2112
2125
|
var q = state.ns ? '?' + outNsQ() : ''
|
|
2113
2126
|
Promise.all([
|
|
2114
2127
|
fetch('/api/qqbot-settings/affinity' + q).then(function (r) { return r.json() }).catch(function () { return {} }),
|
|
2115
|
-
|
|
2128
|
+
// all=1&limit=50: 这张表是"按 key 左连接好感度", 只要 top N 的话排在 N 后面的人永远显示 "—"
|
|
2129
|
+
// (2026-09-15 主人问"小明不是有好感度吗, 怎么显示 0" —— 他 a=0.064 排第 9, 被 top8 截掉)。
|
|
2130
|
+
fetch('/api/qqbot-settings/attitude' + q + (q ? '&' : '?') + 'all=1&limit=50').then(function (r) { return r.json() }).catch(function () { return {} })
|
|
2116
2131
|
]).then(function (rs) {
|
|
2117
2132
|
var aff = (rs[0] && rs[0].items) || []
|
|
2118
2133
|
var att = (rs[1] && rs[1].items) || []
|
|
@@ -2244,11 +2259,21 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2244
2259
|
valueGate: gateEl ? gateEl.value : 'log',
|
|
2245
2260
|
valueMinScore: minEl ? (parseFloat(minEl.value) || 0.5) : 0.5,
|
|
2246
2261
|
}
|
|
2262
|
+
var apNext = null // 附件唤醒新值(账号级): 保存成功后立刻写回本地缓存, 不等接口回读
|
|
2247
2263
|
if (hint) hint.textContent = '保存中…'
|
|
2248
2264
|
fetch(READ + (state.ns ? '?' + outNsQ() : '')).then(function (r) { return r.json() }).then(function (d) {
|
|
2249
2265
|
var cur = (d && d.value) || {}
|
|
2250
2266
|
var lm = Object.assign({}, cur.localModel || {})
|
|
2251
2267
|
if (dirEl) lm.modelDir = String(dirEl.value || '').trim() // 账号级
|
|
2268
|
+
// 附件唤醒开关(账号级,2026-09-15):勾了 = 该类附件无视分数直接唤醒
|
|
2269
|
+
{
|
|
2270
|
+
var apEls = panel ? panel.querySelectorAll('.dk-lm-ap') : []
|
|
2271
|
+
if (apEls.length) {
|
|
2272
|
+
lm.attachmentPassthrough = {}
|
|
2273
|
+
apEls.forEach(function (el) { lm.attachmentPassthrough[el.getAttribute('data-kind')] = !!el.checked })
|
|
2274
|
+
apNext = lm.attachmentPassthrough
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2252
2277
|
lm.overrides = Object.assign({}, (lm.overrides || {}))
|
|
2253
2278
|
lm.overrides[ovKey] = ovNext // 会话级
|
|
2254
2279
|
var patch = Object.assign({}, cur, { localModel: lm })
|
|
@@ -2264,6 +2289,7 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2264
2289
|
lm2.overrides = Object.assign({}, (lm2.overrides || {}))
|
|
2265
2290
|
lm2.overrides[ovKey] = ovNext
|
|
2266
2291
|
if (dirEl) lm2.modelDir = String(dirEl.value || '').trim()
|
|
2292
|
+
if (apNext) lm2.attachmentPassthrough = apNext // 附件唤醒(账号级): 保存后立刻回填, 否则重绘又跳回默认勾选
|
|
2267
2293
|
state.lmCfg = lm2
|
|
2268
2294
|
paintBody()
|
|
2269
2295
|
setTimeout(function () { loadLocalModel() }, 2000)
|
|
@@ -3546,6 +3572,24 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3546
3572
|
+ '<span style="font-size:12px;color:#666">门槛:</span>'
|
|
3547
3573
|
+ '<input class="qqs-inp" id="dk-lm-min" type="number" step="0.01" min="0" max="1" style="width:78px" value="' + minV + '">'
|
|
3548
3574
|
+ '</div>'
|
|
3575
|
+
// 🎬 附件唤醒开关(2026-09-15 主人定):按类型决定"是否**无视分数**直接唤醒"
|
|
3576
|
+
// 勾上 = 该类附件不管分多低都唤醒;没勾 = 交给评分决定(没文字没分 → 不唤醒)
|
|
3577
|
+
{
|
|
3578
|
+
// ⚠️ 2026-09-15 修(主人反馈"关掉图片唤醒, 保存后又跳回勾选"):
|
|
3579
|
+
// 根因=这里只读 status 接口(lm)的字段, 而该接口的 bot.cfg 解析不到实例配置(实测一律
|
|
3580
|
+
// fallback: valueGate=log / 无 attachmentPassthrough) → 永远按默认渲染 → 看着像没保存。
|
|
3581
|
+
// 实际 settings 里早就是 image:false(读接口 ?ns= 已证实)。所以**以 lmCfg(直读配置)为准**,
|
|
3582
|
+
// status 只兜底 —— 与 09-13 修 valueGate 同一个教训: 前端别依赖需要重启才对的 host 字段。
|
|
3583
|
+
var apCfg = ((lmCfg && lmCfg.attachmentPassthrough) || lm.attachmentPassthrough || {})
|
|
3584
|
+
var apDef = { image: true, video: false, voice: false, file: false }
|
|
3585
|
+
body += '<div class="dk-row" style="gap:12px;flex-wrap:wrap;align-items:center;margin:4px 0 2px">'
|
|
3586
|
+
+ '<span style="font-size:12px;color:#666" title="勾上的类型:不管分数都唤醒她;没勾的按评分判(没文字就没分 → 不唤醒)">附件唤醒:</span>'
|
|
3587
|
+
+ [['image', '🖼 图片'], ['video', '🎬 视频'], ['voice', '🎤 语音'], ['file', '📎 文件']].map(function (k) {
|
|
3588
|
+
var on = apCfg[k[0]] === undefined ? apDef[k[0]] : apCfg[k[0]] === true
|
|
3589
|
+
return '<label style="display:inline-flex;align-items:center;gap:4px;font-size:12px;cursor:pointer"><input type="checkbox" class="dk-lm-ap" data-kind="' + k[0] + '"' + (on ? ' checked' : '') + '> ' + k[1] + '</label>'
|
|
3590
|
+
}).join('')
|
|
3591
|
+
+ '</div>'
|
|
3592
|
+
}
|
|
3549
3593
|
body += '<span class="dk-msg" id="dk-lm-hint" style="color:#2f9e44;margin:2px 0"></span>'
|
|
3550
3594
|
// 📊 最近评分(观察期): 让主人直接看到"她是怎么被叫醒的"
|
|
3551
3595
|
// 2026-09-13 二次修: 列表抽成 scoreListHtml + 独立 div, 由 refreshScoreList 做**局部刷新**
|
|
@@ -4253,11 +4297,11 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
4253
4297
|
if (keepScrollOffset == null) box.scrollTop = box.scrollHeight
|
|
4254
4298
|
else box.scrollTop = box.scrollHeight - keepScrollOffset
|
|
4255
4299
|
}
|
|
4256
|
-
// 聊天文本渲染(2026-09-13 引用消息功能):
|
|
4257
|
-
//
|
|
4300
|
+
// 聊天文本渲染(2026-09-13 引用消息功能): 把引用块渲染成"引用样式"气泡(左边框+灰底), 其余正文照常。
|
|
4301
|
+
// 2026-09-15 标记改短([引]…[/引][当前]) —— **新旧都认**, 否则老会话里的英文标记会显示成裸文本。
|
|
4258
4302
|
function chatRenderText(t) {
|
|
4259
|
-
var src = String(t || '')
|
|
4260
|
-
var re = /\[Quoted message begins\]([\s\S]*?)\[Quoted message ends\]/g
|
|
4303
|
+
var src = String(t || '').replace(/\[(?:当前|Current message)\]\s*/g, '')
|
|
4304
|
+
var re = /\[(?:引|Quoted message begins)\]([\s\S]*?)\[\/(?:引|Quoted message ends)\]/g
|
|
4261
4305
|
var parts = []
|
|
4262
4306
|
var m, last = 0
|
|
4263
4307
|
while ((m = re.exec(src)) !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-tools.d.ts","sourceRoot":"","sources":["../src/channel-tools.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AA2ElE,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,eAAO,MAAM,IAAI,wBAAwB,CAAC;AAC1C,eAAO,MAAM,MAAM,UAAY,CAAC;AAShC,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAI/D;AAkFD,mDAAmD;AACnD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"channel-tools.d.ts","sourceRoot":"","sources":["../src/channel-tools.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AA2ElE,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,eAAO,MAAM,IAAI,wBAAwB,CAAC;AAC1C,eAAO,MAAM,MAAM,UAAY,CAAC;AAShC,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAI/D;AAkFD,mDAAmD;AACnD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAk3DvD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,GAAG,KAAK,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCvC"}
|
package/dist/channel-tools.js
CHANGED
|
@@ -2194,8 +2194,47 @@ export async function apply(ctx) {
|
|
|
2194
2194
|
|| process.cwd();
|
|
2195
2195
|
const defs = await loadExtensionTools(extRoot, (ctx.logger ?? console));
|
|
2196
2196
|
extDiag(`apply extRoot=${extRoot} defs=${defs.length} qqCh=${!!qqCh} bridge=${!!bridgeCh}`);
|
|
2197
|
+
// ── 同名扩展工具「热替换」(2026-09-15 主人踩坑后加) ──────────────────────
|
|
2198
|
+
// 症状: 改**已有**扩展工具的逻辑 → tools_reload 报"完成",但跑的还是旧实现
|
|
2199
|
+
// (dsh 的 registry 对重名直接抛 'already registered',这里只能 catch 跳过)。
|
|
2200
|
+
// 根因: 上一版只"新增",没有回收入口。而 `ctx.tools.register()` 其实**返回 disposer**
|
|
2201
|
+
// (dsh-tools 的 `register(definition): () => void`)→ 先 dispose 旧的再注册新的即可。
|
|
2202
|
+
// ⚠️ disposer 不能存在模块变量里: 热刷是用 `?hot=<ts>` 重新 import 本模块的,
|
|
2203
|
+
// 新模块的模块级 Map 是空的、认不出上次注册的东西 → 必须存在 **globalThis 上的
|
|
2204
|
+
// WeakMap(ctx)** 才跨模块实例可见(也顺带不污染 ctx 对象)。
|
|
2205
|
+
// 安全边界: 只 dispose"我们自己注册过、且名字对得上"的工具;别人注册的同名工具照旧走
|
|
2206
|
+
// 原来的 catch 跳过分支(行为不变)。
|
|
2207
|
+
const disposeStore = (() => {
|
|
2208
|
+
const g = globalThis;
|
|
2209
|
+
const KEY = Symbol.for('im-qqbot.extToolDisposers');
|
|
2210
|
+
let store = g[KEY];
|
|
2211
|
+
if (!store) {
|
|
2212
|
+
store = new WeakMap();
|
|
2213
|
+
g[KEY] = store;
|
|
2214
|
+
}
|
|
2215
|
+
return store;
|
|
2216
|
+
})();
|
|
2217
|
+
const myDisposers = (() => {
|
|
2218
|
+
const k = ctx;
|
|
2219
|
+
let m = disposeStore.get(k);
|
|
2220
|
+
if (!m) {
|
|
2221
|
+
m = new Map();
|
|
2222
|
+
disposeStore.set(k, m);
|
|
2223
|
+
}
|
|
2224
|
+
return m;
|
|
2225
|
+
})();
|
|
2197
2226
|
for (const def of defs) {
|
|
2198
2227
|
try {
|
|
2228
|
+
// 同一个 ctx 再来一遍(热刷)= 更新已有工具:先摘掉我们自己上次注册的那份
|
|
2229
|
+
const prev = myDisposers.get(def.name);
|
|
2230
|
+
if (prev) {
|
|
2231
|
+
try {
|
|
2232
|
+
prev();
|
|
2233
|
+
}
|
|
2234
|
+
catch { /* 旧句柄已失效,忽略 */ }
|
|
2235
|
+
myDisposers.delete(def.name);
|
|
2236
|
+
extDiag(`热替换: 先注销旧 ${def.name}`);
|
|
2237
|
+
}
|
|
2199
2238
|
const tool = defineTool({
|
|
2200
2239
|
name: def.name,
|
|
2201
2240
|
description: def.description + ' (用户扩展工具)',
|
|
@@ -2227,7 +2266,10 @@ export async function apply(ctx) {
|
|
|
2227
2266
|
}
|
|
2228
2267
|
},
|
|
2229
2268
|
});
|
|
2230
|
-
ctx.tools.register(tool);
|
|
2269
|
+
const dispose = ctx.tools.register(tool);
|
|
2270
|
+
// 收下 disposer —— 下次(热刷/重装配)靠它做同名替换,不然只能重启宿主
|
|
2271
|
+
if (typeof dispose === 'function')
|
|
2272
|
+
myDisposers.set(def.name, dispose);
|
|
2231
2273
|
ctx.logger?.info?.(`[channel-tools] 已注册扩展工具: ${def.name}`);
|
|
2232
2274
|
diag(`扩展工具注册成功: ${def.name}`);
|
|
2233
2275
|
extDiag(`注册成功: ${def.name}`);
|