@zaofan/dsh-qqbot 1.10.10 → 1.10.11
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 +83 -0
- package/README_EN.md +31 -0
- package/client/qqbot-settings.js +116 -4
- package/dist/channel-tools.d.ts +0 -12
- package/dist/channel-tools.d.ts.map +1 -1
- package/dist/channel-tools.js +202 -20
- package/dist/channel-tools.js.map +1 -1
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/features/msg-history.d.ts +78 -0
- package/dist/features/msg-history.d.ts.map +1 -0
- package/dist/features/msg-history.js +312 -0
- package/dist/features/msg-history.js.map +1 -0
- package/dist/features/msg-recent.d.ts +72 -0
- package/dist/features/msg-recent.d.ts.map +1 -0
- package/dist/features/msg-recent.js +131 -0
- package/dist/features/msg-recent.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +83 -4
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +36 -0
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +87 -1
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/msg-content-cache.d.ts +50 -0
- package/dist/transport/msg-content-cache.d.ts.map +1 -1
- package/dist/transport/msg-content-cache.js +58 -14
- package/dist/transport/msg-content-cache.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -395,6 +395,40 @@ dsh: disabling profile plugin row "mcp-chrome": Plugin ... is incompatible with
|
|
|
395
395
|
|
|
396
396
|
想强行运行,可用官方提供的**确切版本例外**(`dsh plugin allow-version`,或插件管理页里授予),但官方警告"可能崩溃或数据丢失",自行权衡。
|
|
397
397
|
|
|
398
|
+
### 升级 dsh 0.2.1-rc.2 后,发消息直接「本轮运行失败」/ `unknown prompt variable "{{cwd}}"`?
|
|
399
|
+
|
|
400
|
+
dsh **0.2.1-rc.2** 起系统提示词的变量插值改为**严格模式**:组装阶段只注册 `provider`、`model` 两个变量,
|
|
401
|
+
`{{cwd}}` **不再注册**(当前工作目录改由「工作目录服务」作为运行时上下文注入)。于是组装阶段直接抛错,
|
|
402
|
+
**模型请求根本发不出去** —— 表现是「本轮运行失败」,不是模型回话失败:
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
unknown prompt variable "{{cwd}}" ... registered variables: provider, model
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- 官方内置的预设模板新版已删掉这两句,**用官方预设不受影响**;
|
|
409
|
+
- 会挂的是**从旧模板复制出来的自定义预设** —— 副本里的 `prefix` / `suffix` 明文还留着:
|
|
410
|
+
- `由 {{model}} 驱动……当前工作目录 {{cwd}}`
|
|
411
|
+
- `Your working directory is {{cwd}}.`
|
|
412
|
+
|
|
413
|
+
**修复**:把 `{{cwd}}` 那半句删掉(保留 `{{model}}`)。工作目录不会丢,框架会以运行时上下文照常提供给模型。
|
|
414
|
+
预设现在只存在 profile 的 `cordis.patch.yml` 里,可以直接跑仓库自带脚本批量清(只动**用户复制出来的预设明文**,
|
|
415
|
+
不碰内置模板;默认只处理挂了 QQ 工具的预设):
|
|
416
|
+
|
|
417
|
+
```powershell
|
|
418
|
+
# ① 先看会改什么(不写盘)
|
|
419
|
+
node scripts/fix-preset-cwd.mjs --dry-run
|
|
420
|
+
|
|
421
|
+
# ② 真修(自动备份为 cordis.patch.yml.bak-cwdfix-<时间戳>)
|
|
422
|
+
node scripts/fix-preset-cwd.mjs
|
|
423
|
+
|
|
424
|
+
# 其它 profile / 指定文件
|
|
425
|
+
node scripts/fix-preset-cwd.mjs --scan
|
|
426
|
+
node scripts/fix-preset-cwd.mjs --file ~/.dsh/profiles/web/cordis.patch.yml
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
脚本幂等:`suffix:` 删空后会补成 `''`(避免 YAML 解析成 null),重复跑不会二次破坏。
|
|
430
|
+
改完保存即热重载,宿主会重新合并出 `cordis.yml`(可 `grep -c '{{cwd}}'` 确认归零)。
|
|
431
|
+
|
|
398
432
|
## 🤖 自定义事件 + 「让 AI 写」(v1.6.7)
|
|
399
433
|
|
|
400
434
|
普通的互动卡片只能「点按钮 → 回一句固定文本」,能力被编辑器框死。
|
|
@@ -612,6 +646,55 @@ AI 侧的工具叫 **`qq_group_admin`**:
|
|
|
612
646
|
- **不常驻**:拿到凭据立刻关;等扫码最多留 5 分钟;宿主退出时会一起带走(不留孤儿进程)
|
|
613
647
|
- 凭据只存在你自己的机器上,仓库里不含任何凭据
|
|
614
648
|
|
|
649
|
+
## ♻️ 撤回消息与自动撤回(v1.10.11+)
|
|
650
|
+
|
|
651
|
+
机器人**是群管理员**时,可以撤回**普通成员**的消息(官方窗口 2 分钟,超时就撤不了,这是平台限制)。
|
|
652
|
+
|
|
653
|
+
### 三个工具
|
|
654
|
+
|
|
655
|
+
| 工具 | 作用 |
|
|
656
|
+
| --- | --- |
|
|
657
|
+
| `group_recall` | 撤某人最近一条 / 群里最近一条 / 指定 message_id |
|
|
658
|
+
| `group_msg_search` | 按关键词或类型搜台账(**含机器人自己发的**),返回 message_id |
|
|
659
|
+
| `group_recall_batch` | 批量撤(给 msg_ids,或给关键词/类型让它自己挑)+ 可选批量禁言发送者 |
|
|
660
|
+
|
|
661
|
+
`gid` 是**可选**的:不传 = 当前会话所在的群;要撤别的群(机器人也是那个群的管理员)才传。
|
|
662
|
+
|
|
663
|
+
### 自动撤回(设置面板 → 🔨 锤子 → ♻️ 自动撤回)
|
|
664
|
+
|
|
665
|
+
命中关键词或附件类型 → 消息**一进来就撤**,不等 AI。配置项 `groupAdmin.autoRecall`:
|
|
666
|
+
|
|
667
|
+
```yaml
|
|
668
|
+
autoRecall:
|
|
669
|
+
enabled: true
|
|
670
|
+
mode: keep # keep=仍进 AI(只撤消息);drop=连 AI 都收不到
|
|
671
|
+
keywords: ["加我微信"]
|
|
672
|
+
kinds: [] # text / image / voice / video / file,按附件类型命中
|
|
673
|
+
muteSeconds: 0 # >0 时顺带禁言发送者
|
|
674
|
+
dryRun: false # true = 只记日志、不真撤
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
> **实现位置很关键**:挂在 **debounce 之前**的中间件里。
|
|
678
|
+
> 写在 `bot.on('message')` 里会被 debounce 吃掉(它 consume 后自己调 `handleInbound`);
|
|
679
|
+
> 写在 `handleInbound` 里又要等派发,等到了往往已过 2 分钟窗口 —— 两种都实测踩过。
|
|
680
|
+
|
|
681
|
+
## 📒 消息历史台账(v1.10.11+)
|
|
682
|
+
|
|
683
|
+
```
|
|
684
|
+
{dataRoot}/.qqbot/msg-history/
|
|
685
|
+
├── _索引.txt # 目录 ↔ 群名 对照,方便人工辨认
|
|
686
|
+
├── group_<群openid>/
|
|
687
|
+
│ ├── 群名.txt # 文件名就是群名
|
|
688
|
+
│ └── 2026-10-20.jsonl # 一天一个文件
|
|
689
|
+
└── c2c_<对方openid>/
|
|
690
|
+
└── 2026-10-20.jsonl
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
- 一行一条:`message_id` / 发送者 / 昵称 / 类型 / 正文摘要 / 时间 / 是否机器人
|
|
694
|
+
- 默认保留 **7 天**(启动时 + 每 200 次写清理过期文件;目录空了自动删)
|
|
695
|
+
- 入站与出站都记;`group_msg_search` / `group_recall*` 就是读它
|
|
696
|
+
- 与引用补全用的 `.qqbot/msg-content.jsonl` **互不干扰**(后者只存被引用消息的正文与图片路径)
|
|
697
|
+
|
|
615
698
|
## 🚫 无上下文模式(按会话省 token)
|
|
616
699
|
|
|
617
700
|
让某个 QQ 会话**每轮只记得最近几条对话**,更早的历史自动折叠 —— 群聊省 token 利器。
|
package/README_EN.md
CHANGED
|
@@ -187,6 +187,37 @@ npm install --legacy-peer-deps # install-time resolution only; runtime behav
|
|
|
187
187
|
|
|
188
188
|
> Tracked: once upstream #37 is fixed and version lines converge, this section can be removed.
|
|
189
189
|
|
|
190
|
+
### After upgrading to dsh 0.2.1-rc.2: every turn fails with `unknown prompt variable "{{cwd}}"`
|
|
191
|
+
|
|
192
|
+
Starting with **0.2.1-rc.2**, dsh resolves system-prompt variables in **strict mode** and only registers
|
|
193
|
+
`provider` and `model`. `{{cwd}}` is **no longer registered** (the working directory is now injected as
|
|
194
|
+
runtime context by the workspace service), so prompt assembly throws before any model request is sent —
|
|
195
|
+
the symptom is "this turn failed", not a bad model reply:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
unknown prompt variable "{{cwd}}" ... registered variables: provider, model
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- Presets shipped with dsh already dropped those sentences; **official presets are fine**.
|
|
202
|
+
- What breaks are **custom presets copied from an old template** — their `prefix` / `suffix` text still contains:
|
|
203
|
+
- `由 {{model}} 驱动……当前工作目录 {{cwd}}`
|
|
204
|
+
- `Your working directory is {{cwd}}.`
|
|
205
|
+
|
|
206
|
+
**Fix**: delete the `{{cwd}}` clause (keep `{{model}}`). The working directory is not lost — the framework still
|
|
207
|
+
provides it as runtime context. Presets now live only in the profile's `cordis.patch.yml`, and the bundled script
|
|
208
|
+
cleans them up (it only touches **user-copied preset text**, never built-in templates; by default only presets used
|
|
209
|
+
by the QQ channel):
|
|
210
|
+
|
|
211
|
+
```powershell
|
|
212
|
+
node scripts/fix-preset-cwd.mjs --dry-run # preview only
|
|
213
|
+
node scripts/fix-preset-cwd.mjs # apply (backs up to cordis.patch.yml.bak-cwdfix-<ts>)
|
|
214
|
+
node scripts/fix-preset-cwd.mjs --scan # every profile
|
|
215
|
+
node scripts/fix-preset-cwd.mjs --file <path> # a specific file
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The script is idempotent and rewrites an emptied `suffix:` as `''` so YAML does not turn it into `null`.
|
|
219
|
+
Saving the file hot-reloads the profile; verify with `grep -c '{{cwd}}' ~/.dsh/profiles/web/cordis.yml`.
|
|
220
|
+
|
|
190
221
|
### 📱 Deploy on Android — the pocket 24/7 setup
|
|
191
222
|
|
|
192
223
|
[dsh-mobile-apk](https://github.com/kelai141/dsh-mobile-apk) is a community-built **Android shell APK for dsh**
|
package/client/qqbot-settings.js
CHANGED
|
@@ -2277,7 +2277,7 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
2277
2277
|
setInterval(refreshBadge, 20000)
|
|
2278
2278
|
|
|
2279
2279
|
// ── 面板状态(每个实例独立保存, 切回不丢) ──
|
|
2280
|
-
var state = { ns: '', accts: [], gid: '', groups: [], tab: 'chat', sendScope: 'group', sendTo: '', sendName: '', sendText: '', insertCtx: true, targetQ: '', c2cs: [], joins: null, mutes: null, members: null, muteSecs: '60', bindGid: '', bindName: '', msg: '', busy: '', wantPeer: null, lookedUp: false, detected: null, detectedHit: null, chatItems: [], chatMore: false, chatBusy: '', chatErr: '', chatOldest: 0, chatText: '', chatIns: true, outMode: '', outRev: undefined, bpEvents: [], bpSel: null, bpDraft: null, bpExt: null, bpExtBusy: '', bpReq: '', bpReqHint: '', bpSrcText: null, bpSrcPath: '', rosterSel: {}, rosterScope: 'all', rosterQ: '', hubSid: '', hubRev: undefined, hubBusy: '', hubMsg: '', gaEnabled: false, gaPoll: false, gaPollWake: true, gaHubNotify: true, gaNotifyGroup: true, gaInterval: 5, gaMinCount: 1, gaMsg: '', gaBusy: '', bcDraft: null, bcTasks: null, cardMd: '', cardBtns: '', cardGid: '', cardBusy: '', cardQ: '', tgGroups: [], tgCur: '', hammerSub: 'mute', qunLogged: null, qunUin: '', qunGroups: [], qunGc: '', qunKw: '', qunMembers: [], qunCount: 0, qunQr: false, qunMsg: '', qunBusy: false, qunLoading: false }
|
|
2280
|
+
var state = { ns: '', accts: [], gid: '', groups: [], tab: 'chat', sendScope: 'group', sendTo: '', sendName: '', sendText: '', insertCtx: true, targetQ: '', c2cs: [], joins: null, mutes: null, members: null, muteSecs: '60', bindGid: '', bindName: '', msg: '', busy: '', wantPeer: null, lookedUp: false, detected: null, detectedHit: null, chatItems: [], chatMore: false, chatBusy: '', chatErr: '', chatOldest: 0, chatText: '', chatIns: true, outMode: '', outRev: undefined, bpEvents: [], bpSel: null, bpDraft: null, bpExt: null, bpExtBusy: '', bpReq: '', bpReqHint: '', bpSrcText: null, bpSrcPath: '', rosterSel: {}, rosterScope: 'all', rosterQ: '', hubSid: '', hubRev: undefined, hubBusy: '', hubMsg: '', gaEnabled: false, gaPoll: false, gaPollWake: true, gaHubNotify: true, gaNotifyGroup: true, gaInterval: 5, gaMinCount: 1, gaMsg: '', gaBusy: '', bcDraft: null, bcTasks: null, cardMd: '', cardBtns: '', cardGid: '', cardBusy: '', cardQ: '', tgGroups: [], tgCur: '', hammerSub: 'mute', arOn: false, arMode: 'keep', arKws: '', arKinds: [], arMute: 0, arDry: false, arMsg: '', qunLogged: null, qunUin: '', qunGroups: [], qunGc: '', qunKw: '', qunMembers: [], qunCount: 0, qunQr: false, qunMsg: '', qunBusy: false, qunLoading: false }
|
|
2281
2281
|
// 🗂 自定义目标分组(仿 QQ 分组): 2026-09-12 起**host 持久化**({dataRoot}/.qqbot/target-groups.json),
|
|
2282
2282
|
// localStorage 只当秒开缓存 —— 这样 **AI 与主人共用同一份分组**(agent 侧 broadcast_send 可直接写分组名群发),
|
|
2283
2283
|
// 顺带修掉"换个浏览器分组就没了"的老毛病。
|
|
@@ -3245,6 +3245,23 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3245
3245
|
var e4c = panel && panel.querySelector('#dk-ga-wakegroup'); if (e4c) e4c.checked = state.gaWakeGroup
|
|
3246
3246
|
var e5 = panel && panel.querySelector('#dk-ga-interval'); if (e5) e5.value = state.gaInterval
|
|
3247
3247
|
var e6 = panel && panel.querySelector('#dk-ga-mincount'); if (e6) e6.value = state.gaMinCount
|
|
3248
|
+
// 🔨 自动撤回(2026-10-20): 载入 autoRecall 当前值
|
|
3249
|
+
var ar = ga.autoRecall || {}
|
|
3250
|
+
state.arOn = ar.enabled === true
|
|
3251
|
+
state.arMode = ar.mode === 'drop' ? 'drop' : 'keep'
|
|
3252
|
+
state.arKws = (ar.keywords || []).join('\n')
|
|
3253
|
+
state.arKinds = (ar.kinds || []).slice()
|
|
3254
|
+
state.arMute = Number(ar.muteSeconds) || 0
|
|
3255
|
+
state.arDry = ar.dryRun === true
|
|
3256
|
+
var a1 = panel && panel.querySelector('#dk-ar-on'); if (a1) a1.checked = state.arOn
|
|
3257
|
+
var a2 = panel && panel.querySelector('#dk-ar-mode'); if (a2) a2.value = state.arMode
|
|
3258
|
+
var a3 = panel && panel.querySelector('#dk-ar-kws'); if (a3) a3.value = state.arKws
|
|
3259
|
+
var a4 = panel && panel.querySelector('#dk-ar-mute'); if (a4) a4.value = state.arMute
|
|
3260
|
+
var a5 = panel && panel.querySelector('#dk-ar-dry'); if (a5) a5.checked = state.arDry
|
|
3261
|
+
;['text', 'image', 'voice', 'video', 'file'].forEach(function (k) {
|
|
3262
|
+
var el = panel && panel.querySelector('#dk-ar-k-' + k)
|
|
3263
|
+
if (el) el.checked = state.arKinds.indexOf(k) >= 0
|
|
3264
|
+
})
|
|
3248
3265
|
}).catch(function () {})
|
|
3249
3266
|
}
|
|
3250
3267
|
function gaHint(t) {
|
|
@@ -3252,8 +3269,44 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3252
3269
|
var el = panel && panel.querySelector('#dk-ga-hint')
|
|
3253
3270
|
if (el) el.textContent = state.gaMsg
|
|
3254
3271
|
}
|
|
3272
|
+
// 自动撤回页自己的提示行(保存反馈也写这里,2026-10-20)
|
|
3273
|
+
function arHint(t) {
|
|
3274
|
+
state.arMsg = t || ''
|
|
3275
|
+
var el = panel && panel.querySelector('#dk-ar-hint')
|
|
3276
|
+
if (el) el.textContent = state.arMsg
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* 只保存 autoRecall(2026-10-20 修 BUG):
|
|
3280
|
+
* 之前复用 saveGroupAdmin,会把 enabled/轮询这些**不在本页的字段**用 state 旧值一起提交(state.gaEnabled 本页无控件回读)
|
|
3281
|
+
* → 保存自动撤回时顺手把「群管理」关掉了。改为只 patch 本页字段。
|
|
3282
|
+
*/
|
|
3283
|
+
function saveAutoRecallOnly() {
|
|
3284
|
+
arHint('保存中…')
|
|
3285
|
+
fetch(READ + (state.ns ? '?' + outNsQ() : '')).then(function (r) { return r.json() }).then(function (d) {
|
|
3286
|
+
if (!d || !d.value) { arHint('✗ 保存失败: 读取当前设置失败'); return }
|
|
3287
|
+
var cur = d.value
|
|
3288
|
+
var patch = Object.assign({}, cur)
|
|
3289
|
+
patch.groupAdmin = Object.assign({}, cur.groupAdmin || {}, {
|
|
3290
|
+
autoRecall: {
|
|
3291
|
+
enabled: state.arOn === true,
|
|
3292
|
+
mode: state.arMode === 'drop' ? 'drop' : 'keep',
|
|
3293
|
+
keywords: String(state.arKws || '').split(String.fromCharCode(10)).map(function (x) { return x.trim() }).filter(Boolean),
|
|
3294
|
+
kinds: (state.arKinds || []).slice(),
|
|
3295
|
+
muteSeconds: Math.max(0, parseInt(state.arMute, 10) || 0),
|
|
3296
|
+
dryRun: state.arDry === true,
|
|
3297
|
+
},
|
|
3298
|
+
})
|
|
3299
|
+
return fetch(UPDATE, {
|
|
3300
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
3301
|
+
body: JSON.stringify({ ns: state.ns || undefined, patch: patch, expectedRevision: d.revision }),
|
|
3302
|
+
}).then(function (r) { return r.json().catch(function () { return null }) }).then(function (d2) {
|
|
3303
|
+
if (d2 && d2.value) { state.hubRev = d2.revision; arHint('✓ 自动撤回已保存(live 生效,立刻按新规则执行)') }
|
|
3304
|
+
else arHint('✗ 保存失败: ' + ((d2 && d2.error) || '未知错误'))
|
|
3305
|
+
}).catch(function () { arHint('✗ 保存异常') })
|
|
3306
|
+
}).catch(function () { arHint('✗ 读取当前设置失败') })
|
|
3307
|
+
}
|
|
3255
3308
|
function saveGroupAdmin() {
|
|
3256
|
-
state.gaBusy = 'ga'; gaHint('保存中…')
|
|
3309
|
+
state.gaBusy = 'ga'; gaHint('保存中…'); arHint('保存中…')
|
|
3257
3310
|
var doSave = function () {
|
|
3258
3311
|
var trySave = function (cur, rev) {
|
|
3259
3312
|
var patch = Object.assign({}, cur || {})
|
|
@@ -3269,6 +3322,15 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3269
3322
|
notifyGroup: state.gaNotifyGroup,
|
|
3270
3323
|
wakeGroup: state.gaWakeGroup,
|
|
3271
3324
|
},
|
|
3325
|
+
// 🔨 自动撤回(2026-10-20 主人定): 命中关键词/附件类型 → 立刻撤; mode 决定是否进 AI
|
|
3326
|
+
autoRecall: {
|
|
3327
|
+
enabled: state.arOn === true,
|
|
3328
|
+
mode: state.arMode === 'drop' ? 'drop' : 'keep',
|
|
3329
|
+
keywords: String(state.arKws || '').split('\n').map(function (s) { return s.trim() }).filter(Boolean),
|
|
3330
|
+
kinds: (state.arKinds || []).slice(),
|
|
3331
|
+
muteSeconds: Math.max(0, parseInt(state.arMute, 10) || 0),
|
|
3332
|
+
dryRun: state.arDry === true,
|
|
3333
|
+
},
|
|
3272
3334
|
})
|
|
3273
3335
|
patch.groupAdmin = ga
|
|
3274
3336
|
return fetch(UPDATE, {
|
|
@@ -3279,10 +3341,12 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3279
3341
|
if (d2 && d2.value) {
|
|
3280
3342
|
state.hubRev = d2.revision
|
|
3281
3343
|
gaHint('✓ 群组设置已保存(live 生效, 轮询无需重启)')
|
|
3344
|
+
arHint('✓ 自动撤回已保存(live 生效,立刻按新规则执行)')
|
|
3282
3345
|
} else {
|
|
3283
3346
|
gaHint('保存失败: ' + ((d2 && d2.error) || '未知错误') + (d2 && String(d2.error || '').indexOf('changed since') >= 0 ? '(冲突, 请重试)' : ''))
|
|
3347
|
+
arHint('✗ 保存失败: ' + ((d2 && d2.error) || '未知错误'))
|
|
3284
3348
|
}
|
|
3285
|
-
}).catch(function () { state.gaBusy = ''; gaHint('保存异常') })
|
|
3349
|
+
}).catch(function () { state.gaBusy = ''; gaHint('保存异常'); arHint('✗ 保存异常') })
|
|
3286
3350
|
}
|
|
3287
3351
|
fetch(READ + (state.ns ? '?' + outNsQ() : '')).then(function (r) { return r.json() }).then(function (d) {
|
|
3288
3352
|
if (d && d.value) trySave(d.value, d.revision)
|
|
@@ -3930,6 +3994,7 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3930
3994
|
body += '<div class="dk-row" style="gap:6px;margin:2px 0">'
|
|
3931
3995
|
+ '<button class="dk-btn' + (hsub === 'mute' ? ' ok' : '') + '" data-hsub="mute">🔇 禁言</button>'
|
|
3932
3996
|
+ '<button class="dk-btn' + (hsub === 'kick' ? ' ok' : '') + '" data-hsub="kick">👢 踢人</button>'
|
|
3997
|
+
+ '<button class="dk-btn' + (hsub === 'auto' ? ' ok' : '') + '" data-hsub="auto">♻️ 自动撤回</button>'
|
|
3933
3998
|
+ '</div>'
|
|
3934
3999
|
if (hsub === 'mute') {
|
|
3935
4000
|
body += '<div class="dk-row">目标群: ' + groupSelHtml
|
|
@@ -3939,8 +4004,10 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
3939
4004
|
body += '<div class="dk-list" id="dk-member-list"></div>'
|
|
3940
4005
|
body += '<div style="font-weight:700;font-size:13px;margin:8px 0 4px">② 正在禁言中</div>'
|
|
3941
4006
|
body += '<div class="dk-list" id="dk-mute-list"></div>'
|
|
3942
|
-
} else {
|
|
4007
|
+
} else if (hsub === 'kick') {
|
|
3943
4008
|
body += hammerKickHtml()
|
|
4009
|
+
} else {
|
|
4010
|
+
body += hammerAutoRecallHtml()
|
|
3944
4011
|
}
|
|
3945
4012
|
} else if (state.tab === 'roster') {
|
|
3946
4013
|
body += '<div class="dk-row" style="font-weight:700;font-size:13px;margin:2px 0">📇 会话台账:机器人聊过的对象</div>'
|
|
@@ -4380,6 +4447,32 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
4380
4447
|
// ── 🔨 锤子 · 踢人(2026-10-02 主人定)──
|
|
4381
4448
|
// 登录态与 AI **共享**:两边读写同一个 {dataRoot}/qun-cookie.json,
|
|
4382
4449
|
// 所以面板登录完 AI 立刻能用、AI 登录完面板也立刻显示已登录。
|
|
4450
|
+
// ── 🔨 锤子 · 自动撤回(2026-10-20 主人定)──
|
|
4451
|
+
function hammerAutoRecallHtml() {
|
|
4452
|
+
var kinds = [['text', '文本'], ['image', '图片'], ['voice', '语音'], ['video', '视频'], ['file', '文件']]
|
|
4453
|
+
var box = kinds.map(function (k) {
|
|
4454
|
+
return '<label style="display:inline-flex;align-items:center;gap:4px;cursor:pointer;font-size:12px"><input type="checkbox" id="dk-ar-k-' + k[0] + '"' + (state.arKinds.indexOf(k[0]) >= 0 ? ' checked' : '') + '> ' + k[1] + '</label>'
|
|
4455
|
+
}).join('')
|
|
4456
|
+
return '<div class="dk-row" style="gap:8px;flex-wrap:wrap">'
|
|
4457
|
+
+ '<label style="display:inline-flex;align-items:center;gap:4px;cursor:pointer;font-size:12px"><input type="checkbox" id="dk-ar-on"' + (state.arOn ? ' checked' : '') + '> 启用自动撤回</label>'
|
|
4458
|
+
+ '<span style="font-size:12px;color:#666">命中后:</span>'
|
|
4459
|
+
+ '<select id="dk-ar-mode" style="font-size:12px;padding:2px 4px">'
|
|
4460
|
+
+ '<option value="keep"' + (state.arMode !== 'drop' ? ' selected' : '') + '>仍进 AI(只撤回聊天记录)</option>'
|
|
4461
|
+
+ '<option value="drop"' + (state.arMode === 'drop' ? ' selected' : '') + '>直接吞掉(AI 也不看)</option>'
|
|
4462
|
+
+ '</select>'
|
|
4463
|
+
+ '<label style="display:inline-flex;align-items:center;gap:4px;cursor:pointer;font-size:12px" title="只记日志, 不真撤"><input type="checkbox" id="dk-ar-dry"' + (state.arDry ? ' checked' : '') + '> 演练</label>'
|
|
4464
|
+
+ '</div>'
|
|
4465
|
+
+ '<div style="font-weight:700;font-size:13px;margin:8px 0 4px">① 关键词(一行一个,命中任一即撤)</div>'
|
|
4466
|
+
+ '<textarea id="dk-ar-kws" rows="4" style="width:100%;box-sizing:border-box;font-size:12px;padding:6px;border:1px solid #e2d9ff;border-radius:6px" placeholder="例如:加我微信">' + (state.arKws || '') + '</textarea>'
|
|
4467
|
+
+ '<div style="font-weight:700;font-size:13px;margin:8px 0 4px">② 按附件类型(勾上=这类消息直接被撤)</div>'
|
|
4468
|
+
+ '<div class="dk-row" style="gap:10px;flex-wrap:wrap">' + box + '</div>'
|
|
4469
|
+
+ '<div class="dk-row" style="gap:8px;margin-top:8px;align-items:center">'
|
|
4470
|
+
+ '<span style="font-size:12px;color:#666">顺带禁言 <input type="number" id="dk-ar-mute" min="0" value="' + (state.arMute || 0) + '" style="width:64px;font-size:12px;padding:2px 4px"> 秒(0=不禁言)</span>'
|
|
4471
|
+
+ '<button class="dk-btn ok" id="dk-ar-save" style="margin-left:auto">💾 保存</button>'
|
|
4472
|
+
+ '</div>'
|
|
4473
|
+
+ '<div class="dk-msg" style="line-height:1.5;color:#888">执行点在「入站扩展之后」:选"仍进 AI"=扩展与 AI 都照常收到、只是聊天记录里消失;选"直接吞掉"=扩展照常执行、但这条不进 AI。机器人需为群管理员,且官方撤回窗口只有 2 分钟。</div>'
|
|
4474
|
+
+ '<div class="dk-msg" id="dk-ar-hint" style="color:#2f9e44">' + (state.arMsg || '') + '</div>'
|
|
4475
|
+
}
|
|
4383
4476
|
function hammerKickHtml() {
|
|
4384
4477
|
if (state.qunLogged !== true) {
|
|
4385
4478
|
var h = '<div class="dk-msg">踢人需要先登录 QQ 群管理(扫码一次,之后长期免扫;登录态与 AI 共享)。</div>'
|
|
@@ -4528,6 +4621,25 @@ var QQS_CSS = ".qqs-btn{font:inherit;color:#333;background:linear-gradient(180de
|
|
|
4528
4621
|
paintBody()
|
|
4529
4622
|
}
|
|
4530
4623
|
})
|
|
4624
|
+
// 🔨 自动撤回 · 保存(2026-10-20)
|
|
4625
|
+
var arSaveBtn = panel.querySelector('#dk-ar-save')
|
|
4626
|
+
if (arSaveBtn) arSaveBtn.onclick = function () {
|
|
4627
|
+
var kv = panel.querySelector('#dk-ar-kws')
|
|
4628
|
+
state.arKws = kv ? kv.value : ''
|
|
4629
|
+
var on = panel.querySelector('#dk-ar-on'); state.arOn = on ? on.checked : false
|
|
4630
|
+
var md = panel.querySelector('#dk-ar-mode'); state.arMode = (md && md.value === 'drop') ? 'drop' : 'keep'
|
|
4631
|
+
var dr = panel.querySelector('#dk-ar-dry'); state.arDry = dr ? dr.checked : false
|
|
4632
|
+
var mu = panel.querySelector('#dk-ar-mute')
|
|
4633
|
+
state.arMute = Math.max(0, parseInt(mu ? mu.value : '0', 10) || 0)
|
|
4634
|
+
var arr = []
|
|
4635
|
+
;['text', 'image', 'voice', 'video', 'file'].forEach(function (k) {
|
|
4636
|
+
var el = panel.querySelector('#dk-ar-k-' + k)
|
|
4637
|
+
if (el && el.checked) arr.push(k)
|
|
4638
|
+
})
|
|
4639
|
+
state.arKinds = arr
|
|
4640
|
+
state.arMsg = '保存中…'
|
|
4641
|
+
saveGroupAdmin()
|
|
4642
|
+
}
|
|
4531
4643
|
var qLoginBtn = panel.querySelector('#dk-qun-login')
|
|
4532
4644
|
if (qLoginBtn) qLoginBtn.onclick = function () { doQunLogin() }
|
|
4533
4645
|
var qStatusBtn = panel.querySelector('#dk-qun-status')
|
package/dist/channel-tools.d.ts
CHANGED
|
@@ -96,29 +96,17 @@ export declare function syncContextTools(agentCtx: unknown, shouldHave: boolean,
|
|
|
96
96
|
* ⚠️ 可见性不在这里决定 —— 交给 {@link restrictContextTools} 按会话剪枝。
|
|
97
97
|
*/
|
|
98
98
|
/**
|
|
99
|
-
|
|
100
99
|
* 把 `expose: 'all'` 的扩展工具注册到**插件根作用域**(2026-10-10 主人要的"暴露给全部会话")。
|
|
101
|
-
|
|
102
100
|
*
|
|
103
|
-
|
|
104
101
|
* 为什么必须挂在这一层:扩展工具平时由 session-manager 在**每个 QQ 会话**上用 `agentCtx` 注册,
|
|
105
|
-
|
|
106
102
|
* 而 web / 终端等**非 QQ 会话不走那条链** ⇒ 压根看不到它们。想让它们跨通道,
|
|
107
|
-
|
|
108
103
|
* 只能挂到插件根 —— 宿主会让所有会话继承插件作用域的工具(`context_*` 三工具当年也是为此才改成全局)。
|
|
109
|
-
|
|
110
104
|
*
|
|
111
|
-
|
|
112
105
|
* 命名:`<name>@<人设 id>`,并且**每个账号实例各注册一份**(实例各有自己的 preset / appId / 账号数据);
|
|
113
|
-
|
|
114
106
|
* 执行时由注册名里的 tag 反查账号包(见 buildWorkspaceQqPack)—— **名字即归属**。
|
|
115
|
-
|
|
116
107
|
*
|
|
117
|
-
|
|
118
108
|
* 幂等:句柄存 globalThis(跨热刷可见),已注册过的 `name@tag` 不重复注册。
|
|
119
|
-
|
|
120
109
|
* fail-soft:拿不到 tools 服务 / 没有实例 / 目录读不到,一律静默跳过,绝不影响主流程。
|
|
121
|
-
|
|
122
110
|
*/
|
|
123
111
|
export declare function ensureExtToolsGlobal(globalCtx: unknown): void;
|
|
124
112
|
export declare function ensureContextToolsGlobal(globalCtx: unknown): void;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"channel-tools.d.ts","sourceRoot":"","sources":["../src/channel-tools.ts"],"names":[],"mappings":"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"}
|