Switches a bot between qq-bridge role cards and dsh-persona-dafeiyu personas so only one is active, either globally or one persona per QQ conversation. Needs a running qq-bridge.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:HH56715/qq-bridge#path:/plugins/qq-persona-switch
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
一个 DSH 插件:把 qq-bridge 的人格卡和 DSH 人设插件(dsh-persona-dafeiyu)的人设放进同一个开关,并且支持按对话指定——同一个 bot,在 A 群是「小鲸鱼」,在 B 群是「傲娇助手」,私聊又是另一个人。
它解决什么问题
两边的人设是各自独立注入的:
qq-bridge把roles/<名>.md拼进每条提示词(只有一份,全局);dsh-persona-dafeiyu在 DSH 会话装配时再注一个 prompt section(也是全局/工作区级)。
只开一边没问题,两边同时开就会串味 —— 一条回复里混两个人设,而且从界面上看不出是哪边在生效。更麻烦的是,两边都只认「一个」,天生做不了「每个群不同人设」。
这个插件给两种用法:
| 模式 | 适合 | 效果 |
|---|---|---|
| 全局(默认) | 这台 bot 就是这只人格 | 点一张卡,另一侧自动关掉 |
| 按对话 | 一群一设、公私分流 | 每个对话各挑一个;没挑的不带人设 |
前置
| 依赖 | 说明 |
|---|---|
| DeepSeek Harness | >= 0.2.0(用到 webServer 与 systemPrompt 两个服务) |
| dsh-persona-dafeiyu | v0.8.x;没有它,「按对话 + 人格卡」和全局模式仍然可用,只有「分给插件人设」这一半失效 |
| qq-bridge | 可选;>= v0.2.1-r1(已在该版本实测)。没有它就只剩插件人设那一半 |
两侧都是可选的:一边连不上,另一边照样能切,界面上会标出来并给出原因。
安装
本包住在 qq-bridge 的 fork(HH56715/qq-bridge)的 plugins/ 子目录里,按子目录装:
dsh plugin --profile web add github:HH56715/qq-bridge#path:/plugins/qq-persona-switch
装完重启 DSH。零依赖、无构建步骤,不需要联网拉别的包。
手动装(或想先看代码再装):
把
plugins\qq-persona-switch整个目录复制到 DSH 的插件目录:Copy-Item .\plugins\qq-persona-switch C:\Users\<你>\.dsh\plugins\qq-persona-switch -Recurse -Force登记进 profile(加一条
link:依赖 + 把包名加进bundles):node C:\Users\<你>\.dsh\plugins\qq-persona-switch\scripts\install-profile.mjs脚本会先备份
package.json,并且是幂等的 —— 重复跑不会重复加。profile 不在默认位置时用--profile=D:/.../profiles/web指定。这两处手改很容易漏:只加
dependencies不加bundles的话,插件完全不生效,而且不报错。重启 DSH。之后打开:
http://127.0.0.1:3080/api/qq-persona-switch
为什么必须重启:DSH 的 profile 在启动时解析 loader entry,
webServer路由与systemPrompt段落都是那时挂的。
回滚:把 package.json 的备份换回去,删掉 ~/.dsh/plugins/qq-persona-switch,再重启。
配置
写在 profile 的 cordis.patch.yml(或者 ~/.dsh/profiles/web/cordis.patch.yml)里:
- insert:
- id: qq-persona-switch
name: qq-persona-switch
config:
bridgeDir: C:/Users/<你>/path/to/qq-bridge
全部留空也能跑:插件会先自己找 qq-bridge,找到后下面三项都能读出来。找不到才轮到默认值。
| 配置 | 留空时会去读 | 再兜底 |
|---|---|---|
bridgeDir |
—— | 先看 QQ_BRIDGE_DIR;再从 workspaceCwd 和 DSH 进程的 cwd 各自向上找 3 层(每层看本目录与它的 qq-bridge 子目录) |
bridgeBaseUrl |
<bridgeDir>/config.json 的 consolePort |
http://127.0.0.1:3100 |
dshBaseUrl |
同上文件的 dsh.baseUrl |
http://127.0.0.1:3080 |
consoleToken |
同上文件的 consoleToken → <bridgeDir>/state/console-token |
环境变量 QQ_BRIDGE_CONSOLE_TOKEN |
workspaceCwd |
同上文件的 sessionCwd |
—— (插件人设必须有它) |
自动发现认的是目录长相,不是名字:一个有 config.json、并且能认出是桥的目录(存在 src/bridge.js 或 state/console-token,或 package.json 的 name 像 qq-bridge)。所以
D:\bots\my-bot\qq-workspace ← QQ bot 的工作区(插件从这儿上溯)
D:\bots\my-bot\qq-bridge ← 桥,config.json 在这儿
这种「工作区与桥同级」的布局,装完什么都不用填就能直接出来。反过来说:桥放在别的盘、别的层级,就填 bridgeDir 或设 QQ_BRIDGE_DIR,别指望猜。
为什么值得猜一次:市场里一键装完没有配置界面,用户也不会去翻 README —— 不猜的话,打开页面只有一句「没有桥接控制台令牌」,看着就像插件坏了。
令牌每次请求都重新读:桥接重启会重新生成 state/console-token,缓存住只会一直 401。
模式一:全局
点一张卡 = 一次 POST /switch,语义与 qq-bridge 自己的 /api/persona/switch 对齐:
| 目标 | 插件侧 | 桥接侧 |
|---|---|---|
人格卡 X |
关全局 → 切到 workspace 模式 → 清空本工作区人设 | role = X |
插件人设 X |
关全局 → 切到 workspace 模式 → 本工作区人设 = X |
清空 role |
| 「不使用任何人设」 | 同上,本工作区人设 = 空 | 清空 role |
两条硬约束:
- 先写插件,再写桥接。 插件不可达且目标是插件人设时直接失败,绝不先清桥接 role —— 否则 bot 变成完全没有人设,比切换失败更糟。
- 桥接写失败会回滚插件侧。 桥接是另一个进程,端口没开、令牌过期都会让写失败;不回滚就正好停在「两边都关」这个最差状态上。这一步是相对 qq-bridge 原实现的补强。
另一侧连不上时一律降级为告警,能改的那一侧照改。
模式二:按对话
切到「按对话各自指定」后,页面变成一张表:每个出现过的对话一行,各挑一个人设。
它怎么做到的
桥接只在全局注一层,插件人设也只在全局/工作区级生效。要让 A 群和 B 群拿到不同的人格卡,只能是插件自己按会话注入:
- 关掉两侧的全局注入(先把原状态拍进快照,退出时原样还回去)。
- 本插件注册一个
systemPrompt段落,每次组装提示词时:- 从上下文取
sessionId→ 拿qq-bridge/state/sessions.json反查它对应哪个 QQ 对话; - 该对话分到人格卡,就渲染那张卡的正文(渲染规则与桥接逐字一致:按
〔一代〕/〔二代〕筛小节、二代剔掉「按空格分句」这类一代指令、按role.maxInjectChars截断); - 分到插件人设或没分,就返回空串。
- 从上下文取
- 分到插件人设的对话,直接调
set-session-persona(sessionId, personaId),由 dafeiyu 自己注入;分到人格卡的对话,则给它写none: true哨兵,让 dafeiyu 在这个会话闭嘴。两边互相让路,永不叠加。
关键行为
| 情况 | 结果 |
|---|---|
| 没指定的对话 | 不带任何人设(两侧全局都关着),只用预设自带的 |
| 对话还没建立会话(bot 没在那儿说过话) | 先记下,标「待生效」;页面上的「补写」或该对话下次出现的消息会让它生效 |
| 退出按对话模式 | 清掉会话级选择,把全局模式的人设、桥接的 role 按快照还回去 |
| 角色卡文件被删了 | 该对话这段注入变成空,不报错、不影响别的对话 |
按对话模式下的状态(以及进模式前的快照)存在 <bridgeDir>/state/qq-persona-switch.json,与桥接自己的 state/ 放一起,重启不丢。
接口
都是本机回环专用(校验 Host、application/json、Origin):
GET /api/qq-persona-switch 页面
GET /api/qq-persona-switch/state 两侧状态 + 卡片列表 + perChat(模式与每个对话的指定)
POST /api/qq-persona-switch/switch {"source":"role"|"plugin"|"none","id":"..."} 全局切换
POST /api/qq-persona-switch/mode {"mode":"global"|"perChat"} 进出按对话
POST /api/qq-persona-switch/assign {"chatKey":"group:123","source":"role"|"plugin"|"none","id":"..."}
POST /api/qq-persona-switch/sync {} 补写待生效的指定
state 永远回 200,两侧的失败原因摊在 bridge.reachable / bridge.error / plugin.reachable / plugin.error 里 —— 界面要能把「谁挂了」显示出来,所以这里不该用一个 5xx 把整页干掉。
按对话模式下 switch 会回 400:两边状态是刻意清空的,允许整体切换只会让用户以为点了没反应。
开发
node test/core.test.js # 切换语义(谁先写、失败怎么收尾) 12 个用例
node test/http.test.js # 三道门 + 路由接线 + 配置推导 + 端到端 15 个用例
node test/per-chat.test.js # 渲染规则 + 存取 + 进出模式 + 单对话 19 个用例
lib/ 下没有任何第三方依赖,三份测试都能在 Node 里裸跑,不需要先装依赖。npm test 跑全部。
lib/switch-core.js 不 import 任何 DSH 的东西,两个上游都是注入进来的,所以它也可以被别的前端(CLI、别的插件)直接复用。test/http.test.js 里有一组用例会起一个真的本机 HTTP 服务扮演两个上游,跑通「路由 → 客户端 → 上游 → 落盘」整条链。
已知边界
- 依赖
dsh-persona-dafeiyu的私有回环接口/api/dsh-persona-dafeiyu/{status,mutate},路径前缀写死在代码里。上游改路由就会断 —— 断的时候表现是界面上「插件人设连不上」,不会静默失败。 POST /mutate一次只接受一个 action(插件本身的设计,每次落盘),所以一次全局切换是 3 次请求。- 按对话模式依赖
qq-bridge/state/sessions.json的映射(group:<号码>→ DSH session id)。这份映射由桥接维护,插件只读;桥接换了存储格式,按对话就找不到人 —— 表现为「指定了但一直待生效」。 - 人格卡渲染规则是抄的一份(
lib/role-card.js,来自 qq-bridge MIT 源码)。桥接以后改了标记语法,这里不会自动跟着变,需要手动同步;test/per-chat.test.js里有一组同源用例盯着这件事。 - 不做「实时同步」:如果有人在 DSH 自己的设置页里改了人设,这个页面要刷新才会看到。
- 按对话模式期间,桥接侧的全局 role 是被清空的。如果这时有人在 qq-bridge 控制台里重新选了一张全局人格卡,那一层会重新生效、和按对话的注入叠在一起 —— 回到这个页面点一次模式切换(或重新指定一次)就会重新清掉。
- 没有做多工作区:只认一个
workspaceCwd(QQ bot 跑在哪个工作区就填哪个)。
许可
MIT。lib/role-card.js 源自 qq-bridge(MIT)。
Links
More in this category
kingcharleslzy-ai/dsh-purge★ 6104
Community-maintained dsh-purge fork with host-policy patches, prompt and rule settings, diagnostics, separate update-check and install controls, and local backup/rollback.
aureth-labs/dsh-aurabot-qq★ 536
Puts the agent on QQ as a persona character through SnowLuma (OneBot v11): private and group chat, voice notes, stickers, Qzone diary, Bilibili browsing, song covers, image generation, long-term memory and mood. Every feature is a module you can switch off, replace or extend locally — hot-reloaded modules can also rewrite group-reply decisions, prompts, outgoing messages and per-chat model choice. Safe by default (master switch off, dry run on).
ECNU/EduWork#dsh-oidc★ 330
Configurable OIDC login and token-authorized model access, including LiteLLM and opt-in oidc-llm integration, with a standalone DSH Web backend.
techflag/workdsh#enterprise-connection★ 322
Enterprise login, colleague mentions, shared text and files, and authenticated business requests for WorkDSH Desktop 2.0.6-alpha.3+ with WorkDSH Admin; distributed as a release archive.
Yuuyuko-uu/dsh-feiyu-mood★ 258
Gives the companion a mood judged each turn and shown as a short context note, with a panel for mood, emotion dimensions and timeline.
huaizhuanghub/dsh-persona-dafeiyu★ 103
Persona library for DeepSeek Harness: injects the selected persona into the system prompt, offering three built-ins plus import from a file or pasted text, applied either profile-wide or per session from a switcher above the composer.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.