DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Gentaine /

Gentaine/dsh-qq-memory

Verified

DeepSeek Harness 的 QQ 桥接对话表记忆插件:SQLite 存储、自动召回、回忆计数、轮次与定期清理。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@2abb6817

dsh-qq-memory

给 qq-bridge + DeepSeek Harness 用的“对话表记忆”插件。

它把 QQ 聊天记录一行一条写进 SQLite,按话题自动召回旧消息;同时记录每条消息 被回忆的次数和所在对话轮次,并定期清理低价值旧消息。

不调用额外的模型,不注册新工具,因此不会被 qq-chat-v2 的 qq-tool-restrict 白名单挡住,也不需要修改 qq-bridge。


功能

  • 落表:读取 qq-bridge 的 state/social-v2.json,把入站/出站消息写进 SQLite。
  • 自动回忆:在 DSH 每轮第一步,从旧消息里按关键词 + 时间窗挑 1–8 条,作为 source.kind = "qq-table-memory" 的上下文追加给模型。
  • 回忆计数:消息真正进入 prompt 时 recall_count + 1。
  • 对话轮次:按“对方发起 → 我回应”的规则给每条消息标 turn_no。
  • 定期清理:按“时间越久、被回忆越少、这一轮交流次数越少”的优先级删除旧消息。
  • 同会话隔离:不会把 A 群的旧账带进 B 群或私聊。
  • 可导出:list、search、Markdown / CSV / JSON 导出。
  • 可回填:可选脚本从 DSH 会话日志里补更早的消息快照。

前置条件

  • DeepSeek Harness 0.2.x(插件在 0.2.1-rc.3 上开发和测试)
  • Node.js >= 24(使用内置 node:sqlite)
  • 已安装并运行的 qq-bridge
  • qq-bridge 处于 reserved2 模式,并且 state/social-v2.json 正在更新

安装

1. 放到本机

git clone https://github.com/Gentaine/dsh-qq-memory.git `
  "$HOME\.dsh\plugins\dsh-qq-memory"

也可以放在任意目录,后面把路径换成实际路径。

2. 注册到 DSH profile

编辑 ~/.dsh/profiles/<profile>/package.json:

{
  "dependencies": {
    "dsh-qq-memory": "link:<绝对路径>/dsh-qq-memory"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-qq-memory"
      ]
    }
  }
}

在 profile 的 node_modules 里建立链接:

# Windows
New-Item -ItemType Junction `
  -Path "$HOME\.dsh\profiles\<profile>\node_modules\dsh-qq-memory" `
  -Target "<绝对路径>\dsh-qq-memory"

# Linux / macOS
ln -s "<绝对路径>/dsh-qq-memory" "$HOME/.dsh/profiles/<profile>/node_modules/dsh-qq-memory"

如果 profile 里有 pnpm-lock.yaml,还需要让 lock 指向同一个本地路径, 否则 DSH 启动时可能按旧 lock 重装。桌面版没有 lock 时直接重启即可。

3. 配置路径

插件自带的 cordis.patch.yml 使用以下默认值:

  • bridgeStateDir:./qq-bridge/state(相对于 DSH 进程的 cwd)
  • databasePath:~/.dsh/qq-memory/conversations.sqlite3

如果你的 qq-bridge 不在 DSH 工作区的 qq-bridge/ 下,打开 dsh-qq-memory/cordis.patch.yml,取消注释并改成实际路径:

- insert:
    - id: qq-memory
      name: dsh-qq-memory
      config:
        enabled: true
        bridgeStateDir: '/absolute/path/to/qq-bridge/state'
        databasePath: '/absolute/path/to/conversations.sqlite3'
        # ...

Windows 路径请用单引号包起来,例如:

        bridgeStateDir: 'D:\qq-bridge\state'

4. 重启 DSH

插件在 bundle 装配阶段加载,改完必须重启 DSH。

5. 确认

node dsh-qq-memory/scripts/qq-memory.mjs stats `
  --db "$HOME/.dsh/qq-memory/conversations.sqlite3"

看到消息数、会话数、总回忆次数、最大轮次就说明表已经建好。


表结构

CREATE TABLE messages (
  id           INTEGER PRIMARY KEY AUTOINCREMENT,
  qq_key       TEXT    NOT NULL,       -- group:群号 / private:QQ号
  session_id   TEXT,                   -- qq-bridge 映射的 DSH session
  seq          INTEGER,                -- 桥接本地序号
  message_id   TEXT,                   -- QQ message_id
  sent_at      INTEGER NOT NULL,       -- 毫秒时间戳
  sender       TEXT,
  user_id      TEXT,
  direction    TEXT    NOT NULL CHECK (direction IN ('in', 'out')),
  text         TEXT    NOT NULL,
  search_text  TEXT    NOT NULL DEFAULT '',
  recall_count INTEGER NOT NULL DEFAULT 0,
  turn_no      INTEGER NOT NULL DEFAULT 1,
  dedupe_key   TEXT    NOT NULL,
  raw          TEXT,
  ingested_at  INTEGER NOT NULL
);

索引:

CREATE UNIQUE INDEX messages_dedupe ON messages (qq_key, dedupe_key);
CREATE INDEX messages_key_time ON messages (qq_key, sent_at);
CREATE INDEX messages_key_direction_time ON messages (qq_key, direction, sent_at);
CREATE INDEX messages_cleanup ON messages (qq_key, turn_no, sent_at);

去重优先级:message_id → seq → 内容哈希。同一条消息被 recentMessages 和 unread 同时带上时只会留一行。

轮次 turn_no

  • 第一轮从第 1 条消息开始;
  • 当“上一条是我发的、下一条是对方发的”时,进入下一轮;
  • 连续几条对方消息算同一轮,我连续补充几句也算同一轮。

所以一轮通常就是“对方发起 → 我回应”。清理规则里的“交流次数”只数这一轮里 对方发的消息条数,不是总消息条数:我连续补很多句不会把这一轮撑成“高价值长讨论”。

回忆次数 recall_count

buildInjection 真正把某条旧消息塞进注入块时,调用 store.markRecalled(usedIds), 对应行 recall_count + 1。被长度截断、没进入 prompt 的消息不计数;用 CLI 查看也不计数。

旧库第一次打开时会自动补 recall_count=0 并按上述规则重算 turn_no。


定期清理

默认每 6 小时检查一次,启动 10 秒后也检查一次。

只有单个会话超过 maxMessagesPerConversation(默认 5000 条)才触发;并且:

  • 只清 minAgeDays(默认 14 天)以前的消息;
  • 不动最近 keepRecentTurns(默认 30)轮。

对剩下的候选,按清理优先级从高到低删,每次最多 batchSize 条:

priority = ageScore * 0.5
         + recallScore * 0.3
         + turnExchangeScore * 0.2

ageScore          = min(1, 消息年龄天数 / ageSaturationDays)  // 越老越高
recallScore       = 1 / (1 + recall_count)                    // 越少被回忆越高
turnExchangeScore = 1 / (1 + 这一轮里对方发的消息条数)        // 交流次数越少越高

默认 ageSaturationDays = 90,权重为 age=0.5, recall=0.3, turnLength=0.2。 优先级越高越先删:

  • 很久没提、从没被回忆、当时只有一两句的寒暄最先走;
  • 经常被想起的、最近 30 轮的、长讨论里的关键消息留得更久。

配置

配置在 cordis.patch.yml 的 config 块:

字段 默认 作用
enabled true 总开关
bridgeStateDir ./qq-bridge/state 桥接状态目录
databasePath ~/.dsh/qq-memory/conversations.sqlite3 SQLite 路径
minAgeHours 3 只把 N 小时以前的消息当“旧记忆”
maxInjectedMessages 8 一次最多注入几条
maxInjectedChars 1800 注入块字符上限
cooldownTurns 5 普通关键词命中的冷却轮数
memoryIntentCooldownTurns 1 明确记忆问法的冷却轮数
queryLookbackMinutes 120 用最近多少分钟的消息当查询
candidateLimit 2500 单次检索最多扫多少条旧消息
minScore 2.2 关键词命中门槛
pollMs 15000 定时补录间隔
timeZone Asia/Shanghai 展示时间用的时区
debug false 打印每次导入的条数
cleanup.enabled true 定期清理总开关
cleanup.intervalHours 6 每隔多少小时检查一次
cleanup.minAgeDays 14 只清 N 天以前的消息
cleanup.keepRecentTurns 30 保留最近 N 轮
cleanup.maxMessagesPerConversation 5000 单会话超过多少条才触发
cleanup.batchSize 200 单次最多删多少条
cleanup.ageSaturationDays 90 年龄多少天后 ageScore 达到 1
cleanup.weights.age 0.5 时间权重
cleanup.weights.recall 0.3 被回忆次数权重
cleanup.weights.turnLength 0.2 交流次数权重(只数对方消息)

改 cordis.patch.yml 后重启 DSH。


命令行

DB="$HOME/.dsh/qq-memory/conversations.sqlite3"
node scripts/qq-memory.mjs stats --db "$DB"
node scripts/qq-memory.mjs list --db "$DB" --limit 30
node scripts/qq-memory.mjs search --db "$DB" --query '训练集'
node scripts/qq-memory.mjs export --db "$DB" --format markdown --out ./exports/对话表.md

Windows PowerShell:

$db = "$HOME\.dsh\qq-memory\conversations.sqlite3"
node scripts/qq-memory.mjs list --db $db --limit 30

list 和导出都带 turn_no(轮次)与 recall_count(被回忆次数)两列。

CSV 写文件时默认加 UTF-8 BOM,Excel 直接打开不会把中文显示成乱码;管道输出默认不带 BOM, 需要纯净 UTF-8 时加 --no-bom。

预览 / 执行清理

# 只算不删(用很小的上限做演示)
node scripts/qq-memory.mjs cleanup --db $db `
  --dry-run --max 100 --min-age-days 0 --keep-turns 0

# 按默认参数真正执行
node scripts/qq-memory.mjs cleanup --db $db

默认规则下,只有会话超过 5000 条、消息超过 14 天、且不在最近 30 轮,才可能进入候选。

回填更早的会话日志

qq-bridge 的 recentMessages 只保留最近 100–200 条。DSH 会话日志里还留着模型过去读过的 qq_get_* 消息快照,可以补进表里:

node scripts/backfill-session-logs.mjs --db $db --dry-run
node scripts/backfill-session-logs.mjs --db $db

脚本只读 ~/.dsh/sessions 和桥接 sessions.json,不会改桥接状态;重复跑不会翻倍。


工作方式

qq-bridge 发给 DSH 的 user/message 是一大段唤醒提示词,真正的群消息在 mcp__snowluma__qq_get_* 的 tool result 里。按 DSH 事件反推既绕又容易漏。

桥接自己已经把入站/出站消息写进 state/social-v2.json 的 recentMessages, 字段稳定(seq/messageId/sender/userId/text/isSelf/time),直接读它最贴近 QQ 的真实对话。

插件读取文件,不修改桥接状态,也不要求桥接进程额外装插件。

注入形式是一条 user 角色、source.kind = "qq-table-memory" 的上下文消息,不是系统提示词, 也不是 QQ 群消息。这样:

  • 不破坏 system prompt 的前缀缓存;
  • 不会在 transcript 里被误认为群友发言;
  • qq-bridge 不会把它当成新 QQ 消息。

已知限制

  • 历史来源有限:桥接只保留最近 100–200 条;回填脚本也只能补到模型当时读过的快照。
  • 纯词法检索:没有向量/同义词语义,“昨晚那个东西”这类没有具体词的问法只能靠时间窗兜底。
  • 不生成摘要:注入原始片段,不是人物小传。
  • 清理是硬删除:被清掉的消息不会进归档;需要留档请定期备份 SQLite 文件。
  • 同会话作用域:默认不做跨群/跨私聊共享。
  • 明文落盘:聊天内容原样进 SQLite,目录应按隐私数据处理。
  • 坏 sender 兜底:SnowLuma 偶尔会把带控制字符的二进制串写进 sender,插件会用 群友<QQ 后四位> 代替。

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 2abb68170b8f

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout