world-channel-bridge
让官方 QQ 机器人(以及任何走会话的外部渠道)说的话,真正进入「世界控制台」里那个人格的记忆。
零依赖、零硬编码路径、可跨机器复用。装完即忘,不需要再改任何代码。
A zero-dependency DSH/Cordis plugin that registers external-channel sessions (official QQ bot, WeChat, …) into the world room workspace, so their conversations actually reach
dsh-world's memory archive.
它解决什么问题
DSH 的世界插件(dsh-world)只归档某个工作区名单里的会话:
归档范围 = 工作区「她/他的房间」的 sessionIds 名单
而官方 QQ 插件(@tencent-connect/dsh-qqbot)创建会话时,只设置了 cwd 和 agent preset,没有把会话登记进工作区名单。于是出现一个很隐蔽的现象:
| 层面 | 状态 |
|---|---|
| QQ 能收消息、能回话 | ✅ 正常 |
| 会话确实建在她的房间目录下 | ✅ 正常 |
| 会话进入记忆归档 | ❌ 被过滤器跳过 |
结果就是「数据通、记忆不通」:QQ 上聊得再多,网页端的她也不知道;日记里也没有 QQ 的痕迹;会话一压缩,那些对话就永久消失。
本插件补的就是这一步登记动作。
QQ 消息 ──► im-qqbot 建会话(cwd = worlds/default/room)
│
▼
✗ 缺少 attachSession() ← 断点在这里
│
world-channel-bridge(本插件)
│ 发现「cwd 在房间目录、但不在名单」的会话
▼
✓ workspace.attachSession(sessionId)
│
▼
dsh-world 归档钩子接管 ──► archive.sqlite(记忆正本)
└► 日记 / 活动 / 长期记忆
为什么它是安全的
- 只调用宿主公开 API(
workspaceRegistry、sessionPersistence),不直接改任何数据文件; - 宿主
attachSession()自带硬校验:会话的 cwd 必须等于工作区路径,否则抛错。所以插件不可能把无关会话塞进她的记忆; - 幂等:已在名单里的会话直接跳过,重复巡检不会产生重复记录;
- 失败只告警,不会拖垮 DSH 服务。
快速安装
方式一:git clone(推荐)
git clone https://github.com/mhggv/world-channel-bridge.git
cd world-channel-bridge
sudo ./install.sh
方式二:下载 Release 包
curl -L -o wcb.tar.gz https://github.com/mhggv/world-channel-bridge/releases/latest/download/world-channel-bridge-1.2.0.tar.gz
tar xzf wcb.tar.gz && cd world-channel-bridge
sudo ./install.sh
方式三:交给 DSH 自己的插件机制
dsh plugin --profile web add /path/to/world-channel-bridge
install.sh 做的事和这条命令一致:把插件登记成 profile 的正式依赖(dependencies)并加入 dsh.profile.bundles。包里自带 cordis.patch.yml 且在 package.json 的 dsh.bundle.patch 中声明,DSH 会按 bundle 的方式把它挂进 loader,不用手工改任何 YAML。
脚本会自动完成:
- 探测
DSH_HOME、profile 目录、node、systemd 服务名和运行用户(都可用参数覆盖); - 把插件放到
$DSH_HOME/plugins/world-channel-bridge; - 在 profile 的
node_modules建软链(绝对路径,不会因目录层级算错); - 在 profile 的
package.json登记dependencies+dsh.profile.bundles(幂等,自动备份原文件)—— 这一步决定它会不会出现在 Web 的「插件管理 → 已安装」里; - 自动清理旧版(≤1.1.0)写在
cordis.patch.yml里的手动条目,避免和 bundle patch 重复挂载; - 校验 YAML 可解析、模块可被宿主导入;
- 重启服务并回读日志确认插件真的加载了。
常用参数:
./install.sh --home /path/to/dsh-home --profile /path/to/dsh-home/profiles/web
./install.sh --no-restart # 只装不重启
./install.sh --link # 不复制文件,直接软链到当前目录(便于反复调试)
./install.sh --dry-run # 只打印将要做什么
装完在 Web 里哪里看得到它
DSH Web 里有两个容易混淆的地方,判定依据完全不同:
| 位置 | 读的是什么 | 你会在里面看到 |
|---|---|---|
| 设置 → 插件清单(Plugin Inventory) | 运行时 loader 的 entry 投影 | 任何已挂载的 entry,包括手工插入的 |
| 设置 → 插件管理 → 已安装(Plugin Manager) | profile package.json 的 dependencies + dsh.profile.bundles |
只有正式登记的包 |
所以:
- 只在
cordis.patch.yml插一条、再往node_modules塞个软链 —— 插件能跑、日志正常、记忆确实在写,但「已安装」列表里永远是空的。这是 1.1.0 及更早版本的做法。 - 登记进
package.json(install.sh或dsh plugin add都会做)—— 列表里出现world-channel-bridge,显示版本、可停用/启用。
./doctor.sh 的第 4 项会直接告诉你当前是哪一种状态;若显示"旧式安装",重跑一次 install.sh 即可迁移(幂等,会自动清掉旧条目)。
非 systemd 环境(Windows / macOS / 前台启动)
./install.sh --home <DSH_HOME> --profile <profile目录> --no-restart
然后手动重启你的 DSH 进程即可。Windows 下请用 Git Bash 或 WSL 执行。
配置
全部可选。想调就改 profile 的 cordis.patch.yml 里这个条目:
- insert:
- id: world-channel-bridge
name: 'world-channel-bridge'
config:
intervalMs: 30000 # 巡检间隔(毫秒),新会话最多延迟这么久被登记
worlds: ['default'] # 只处理这些世界;留空 = 全部
roomDirs: [] # 显式指定房间目录;留空 = 自动发现
worldNamePattern: '' # 自定义房间目录识别正则;留空 = .../worlds/<名>/room
dshHome: '' # 显式指定 DSH_HOME;留空 = 环境变量 / 自动探测
dryRun: false # true = 只写日志不登记(排障用)
verbose: false # true = 每轮巡检都打日志
改完重启服务生效。
大多数情况下一个都不用配:房间目录是从工作区注册表里按
.../worlds/<名>/room自动识别的,换机器、加世界、改目录都不用动代码。
验证
./doctor.sh
一条命令体检 6 项:插件文件、模块链接、代码可导入、配置条目与 YAML、服务状态与运行日志、各房间已登记的会话数 + 记忆正本记录数。全绿就是真的在跑。
单看日志:
journalctl -u dsh-web -n 200 | grep world-channel-bridge
正常长这样:
[world-channel-bridge] v1.2.0 已就绪:监控 1 个世界房间、3 个渠道会话
[world-channel-bridge] 已登记会话 session-xxxx →「房间标题」,该渠道的对话将进入记忆归档
双向验收(最有说服力):
- 在 QQ 上告诉她一件新鲜事 → 去网页端问她,她应该记得;
- 在网页端告诉她一件事 → 去 QQ 上问她,她也应该记得。
换机器怎么用
- 新机器装好 DSH、跑起
dsh-world和 QQ 插件,确认两边都能聊; - 把本目录拷过去,
sudo ./install.sh; ./doctor.sh看到房间和会话数就成。
不需要改代码、不需要记路径、不需要手动改 YAML。如果新机器的目录结构特殊,用 --home / --profile 指定即可。
若要连历史记忆一起搬,另行迁移
worlds/<名>/archive.sqlite、worlds/<名>/room、sessions/等数据, 并保证路径与旧机一致(会话 header 里钉的是绝对路径)。本插件只负责「新对话进得来」。
排障
| 现象 | 原因 | 处理 |
|---|---|---|
日志里完全没有 world-channel-bridge |
插件没被加载 | doctor.sh 看「配置条目」和「模块链接」;注意 patch 条目必须是 - insert: 形式,直接用顶层 - id: 只能覆盖已有插件,新插件会被静默跳过 |
| 有加载日志但会话数一直是 0 | 渠道会话的 cwd 不是房间目录 | 看 doctor.sh 里房间 path,对比 QQ 会话的 cwd;必要时在 QQ 插件配置里把 cwd 指到 worlds/<名>/room |
登记失败:its cwd resolves to '...' |
路径不一致(软链/尾斜杠) | 插件已做 realpath 兜底;仍失败则说明该会话 cwd 确实不是房间目录 |
| 已登记但旧对话没进记忆 | 归档器只增量扫 | 重启服务触发一次全量回扫(catchUp) |
| 换了 Node 版本后报语法错 | Node < 20 | 插件用了较新的语法,升级 Node 到 20+ |
卸载
sudo ./uninstall.sh # 摘配置 + 删文件 + 重启
sudo ./uninstall.sh --keep-files # 只摘配置,保留文件
已归档的记忆不会被撤销;只是之后新产生的渠道对话不再进入记忆。
目录结构
world-channel-bridge/
├── index.js # 插件本体(唯一的运行时文件,零依赖)
├── cordis.patch.yml # DSH 清单:声明本插件要插入的配置行(dsh.bundle.patch 指向它)
├── package.json # 含 dsh.bundle / dsh.compatibility 字段
├── install.sh # 一键安装(自动探测 + 幂等)
├── uninstall.sh # 一键卸载
├── doctor.sh # 体检
├── tools/
│ ├── patch-edit.mjs # 安全增删 cordis.patch.yml 条目(块级编辑,不动其它配置)
│ ├── yaml-check.mjs # 校验配置可解析(兼容宿主自定义的 !!js 标签)
│ └── self-test.mjs # 无宿主自测:node tools/self-test.mjs
├── .github/workflows/ci.yml # CI:语法 + 自测 + 脚本检查(Node 20/22)
├── .github/ISSUE_TEMPLATE/ # Issue 模板(bug 反馈强制附 doctor.sh 输出)
├── LICENSE # MIT
└── README.md
附:兼容范围
依赖宿主两个 API,自 DSH 0.2.x 起稳定:
workspaceRegistry.resolveByPath(path)/workspaceRegistry.entitiesworkspace.attachSession(sessionId)sessionPersistence.list()(返回项含header.cwd、header.id)
任一 API 缺失时插件只告警、不崩溃,服务照常启动。
| 实测组合 | 结果 |
|---|---|
@deepseek-ai/dsh 0.2.1-rc.5 + dsh-world 0.6.6 + @tencent-connect/dsh-qqbot 0.5.5 |
官方 QQ 会话自动登记、归档实时写入、重启回扫补齐 |
版本 1.2.0
许可证
MIT © 2026 mhggv
欢迎提 Issue / PR。如果这个插件省了你几个小时,给个 Star 就行。
No comments yet. Be the first to write one.