This project is an open-source preset plugin for DeepSeek Harness. It does NOT contain any game assets (Live2D models, artwork, audio). The bundled persona text is adapted from the open-source Cyrene-Agent project (MIT, Copyright (c) 2026 Playa). Cyrene (昔涟) and Honkai: Star Rail belong to HoYoverse/miHoYo. This is an unofficial fan tool — non-commercial use only.
使用前请读 风险提醒 —— 自定义风格与角色扮演模式都会影响模型输出,且与上游人设存在已知冲突。
dsh-cyrene
为 DeepSeek Harness(DSH)提供两套角色 agent 预设:
| 预设 | 显示名 | 能力 |
|---|---|---|
preset/chat/ |
纯文本对话模式 | 整份替换系统提示词,纯以角色人格对话;只保留联网搜索与表情包,不挂文件 / Shell / 设备工具 |
preset/cyrene-work/ |
昔涟工作模式 | 同样用角色人设整份替换系统提示词,但带全套工具(文件 / Shell / 计划 / 目标 / 子代理 / workflow);干活时也保持她的身份与说话方式 |
两者共享同一份长期记忆(<DSH_HOME>/cyrene-memory.md),所以同一个角色在两个模式里的记忆是连续的。
⚠️ 禁止商业使用。 本项目为非官方同人作品,与 HoYoverse / 米哈游无任何关联、 背书或赞助关系。角色 IP 归 HoYoverse / 米哈游所有。随包的人设文本整理自开源项目 Cyrene-Agent(MIT,Copyright (c) 2026 Playa)。使用前请先读
THIRD_PARTY_NOTICES.md。
关于随包的人设文本
两套预设都已包含可用的角色人设,装完即可对话。人设文本来自开源项目
Cyrene-Agent(MIT),
并已取得原作者同意。许可与归属细节见
THIRD_PARTY_NOTICES.md。
两套预设用的是上游不同的提示词组,这不是随意挑的:上游
mode-prompt-profile.ts把各模式拆成不同文件——对话模式用chat_system+chat_identity+soul+canon_quotes, 工作模式用work_system+work_identity+work_remark+canon_quotes_lite, 通用工具规范另接tool_usage(且只在非对话模式接)。本仓库照搬这套组合,一字未改。⚠️ 注意上游在工作模式里刻意不用
soul.md,改用精简台词集 —— 也就是说原应用的 "工作模式人设"本来就是轻量版。觉得干活时不够像角色,第一个该调的是这里 (把soul.md加回去),不是本插件的装配机制。
授权凭据:原作者在 issue #137 公开回复同意使用,并将该 issue 以 closed as completed 结案(2026-10-03)。
上游本就是 MIT,本仓库已按 MIT 要求保留版权声明与许可证全文 (
LICENSES/Cyrene-Agent-LICENSE.txt); 上述确认属礼貌性征询,不构成额外授权条件。⚠️ 该确认明确排除 Live2D 模型——模型需另行向模型原作者取得授权。 本仓库不包含任何模型或美术资源;日后若要加入,必须自行取得授权,不得援引本确认。
你也可以完全不用它:清空或替换 prefix: 里的内容即可,本插件的装配机制不依赖具体人设
(写法见 docs/writing-your-preset.md)。
⚠️ 原作者的同意覆盖的是提示词文本本身;角色 IP(昔涟及《崩坏:星穹铁道》 相关内容)归 HoYoverse / 米哈游所有,原作者无权代为授权,非商业使用条款继续适用。
本仓库不包含任何 Live2D 模型、立绘、美术资源或音频——这些不在随包范围内, 也不在 MIT 授权范围内。
它解决什么问题
在 DSH 里给模型套角色人格,常见的做法是把人设写进 AGENTS.md 或
dsh-system-prompt 的 personaPrefix。但那两条路都是叠加:
顺序 −1000 You are an AI agent powered by DeepSeek Harness. ← 固定开场白,去不掉
顺序 0 你的角色人设 ← 你加的内容
中间 工具规范、编码代理行为准则…… ← 一直压着角色
结果就是角色聊着聊着又会冒出编码代理的口气——「有什么要办的事吗」「要不要我帮你处理」, 或者拿环境描写当开场。这不是措辞问题,是提示词分层问题。
本插件用的是另一条路:dsh-persona 的 complete: true。
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
complete: true # 这套提示词就是全部系统提示词
includeRuntimeContext: false
complete: true 会遮蔽其余全部提示词段,包括那句固定开场白和第一方工具规范。
角色人设成为唯一的系统提示词,没有竞争层。
安装
1. 装插件本体
⚠️ 别用
dsh plugin --profile <你的 profile> add dsh-cyrenenpm 上有一个同名的
dsh-cyrene,那是别人的「昔涟主题 + Live2D 桌宠」插件, 它的exports里只有.和./client—— 没有本项目需要的./nickname/./memory/./worldbook/./style。装错之后的现象很有迷惑性:预设能选到,但
设置 → 内置插件里那几行全是never started,预设标「加载失败」——看着像装上了,其实一个都没起来。 详见下面的排查。
从仓库装才装得到本项目(dsh plugin 只是 pnpm 的转发,link: 指本地目录,不需要联网):
# 1) 先拿到仓库
git clone https://github.com/Cyrene-xl/DSH-Cyrene
# 或者从 Releases 页下载 zip 解压
# 2) 装进「你正在用的那个 profile」
dsh plugin --profile <你的 profile> add link:/你的路径/DSH-Cyrene
# Windows: link:D:/你的路径/DSH-Cyrene
# 3) 重启 DSH(新加的行要重新组合 profile 才生效)
cordis.patch.yml 是纯 insert 形式,不往机器上写任何硬编码路径。
装好、重启之后,设置 → 通用里会多四行:「表达风格」「称呼偏好」「角色扮演模式」和「检查更新」
(本包的浏览器半边 client.js 渲染;主机半边 index.js 提供 /cyrene-nickname-api
与 /cyrene-scene-api)。这两行依赖 dsh plugin add 这一步 —— 只拷预设、没装插件本体
的部署里,预设照常能跑,只是设置页没有这两行:直接改 <DSH_HOME>/cyrene-scene.json
(开关)或 dsha-brand-rename.json(称呼)一样生效。
2. 装预设(必须单独做一步)
DSH 没有公开的 agent 预设安装 API——dsh-agent-presets 只按
~/.dsh/.agent-presets/<id>/ 目录扫描发现预设,不对外暴露写入接口。
所以预设必须拷贝到位:
# 先看会改什么(只读,不写任何文件)
./scripts/install.sh --dry-run # 或 node scripts/install.mjs --dry-run
# Linux / macOS(需要 Node 22+,脚本只是 install.mjs 的入口)
./scripts/install.sh --yes
# 任意平台(含 Windows,无 shell 依赖,Node 22+)
node scripts/install.mjs --yes
代际探测:脚本默认自动判断 0.1.x(目录扫描)还是 0.2.0+(声明式注册表)。 探测不到时会打印警告并且只写目录;桌面端等特殊布局可显式指定:
node scripts/install.mjs --yes --generation=declarative。为什么要
--yes:安装会写<DSH_HOME>/.agent-presets/和 profile 的cordis.patch.yml,属于 Profile 变更。脚本会先打印一次性计划,写入前要求 精确确认(终端里手输yes,或显式--yes);覆盖前备份、落盘原子 替换、装完跑健康检查,失败自动回滚。全过程记在<DSH_HOME>/.cyrene-install-journal.json,--rollback可恢复到安装前。
为什么没有 Windows 专用脚本
以前有
scripts/install.ps1和install.bat,已经删掉了。原因很直接:我没有自己的电脑,这一整套是在 DSH App(Android)里做的 —— 开发环境里没有 Windows、也没有 PowerShell,那两个脚本我根本没法实测, 只能靠用户反馈来回试,连试三轮都没在真机上跑通。
与其留一条没人能验证的路,不如只留一条能跑通的。Windows 用户请直接用
node scripts/install.mjs—— 跨平台、无 shell 依赖,已实测过安装 / 幂等 / 卸载。要是它在你的 Windows 上不适配:那大概率得等我以后有了自己的电脑, 才能把那部分补回来重做一遍。在那之前上面这条
install.mjs就是 Windows 的路。 完整的 Windows 说明(怎么装、六条实测坑、将来怎么恢复 PS 脚本)见docs/windows.md。
脚本会把 preset/ 下的每个子目录装成对应的预设:
preset/chat/ → ~/.dsh/.agent-presets/chat/,
preset/cyrene-work/ → ~/.dsh/.agent-presets/cyrene-work/。
⚠️ DSH 0.2.0 及以上:目录方式不会生效(而且不报错)
0.2.0 起预设改为声明式注册表,官方文档原话是「注册表不扫描目录,也不接受 preset 路径」。此时
scripts/install.*拷目录会照常成功、照常打印"已安装", 但预设不会出现在界面上,也没有任何报错。2026-10 起这一步已经自动化了:只要先把插件装进 profile(安装第 1 步), 再跑同一个脚本,它会:
- 认出哪些 profile 属于"声明式注册表"那一代(看 profile 的
node_modules/@deepseek-ai/dsh-agent-preset在不在);- 把两套预设的声明自动写进该 profile 的
cordis.patch.yml(先备份、幂等, 已有内容只追加不覆盖,空补丁的[]会被正确替换);- 同时仍写一份目录副本 —— 同一台机器上若有 0.1.x 的 profile,那边靠它。
所以 0.2.0+ 用户不需要再手动跑
export-preset-declarations再手贴。 装完重启 DSH 即可。为什么声明不直接塞进本包的
cordis.patch.yml(那样"装插件即注册"更省事): 那一行在 0.1.x 上根本解析不到,实测会让整个 DSH 起不来 (ERR_MODULE_NOT_FOUND)。所以按代数分流,而不是一刀切。手贴的路仍然保留(profile 在非默认位置时用)。注意生成的内容自带
- insert:那层,别去掉 —— patch 里不带insert而带id的条目是"按 id 覆盖已有条目", 少了它我们的声明会被静默跳过。
预设 id 是自动发现的(扫描含 agent.cordis.yml 的子目录),所以以后新增预设
只要在 preset/ 下新建目录,四个安装脚本都不用改。
如果目标已存在且内容不同,默认拒绝覆盖(避免抹掉你自己填的人设),
要覆盖得加 --force / -Force,会先自动备份。
$DSH_HOME 环境变量可覆盖默认的 ~/.dsh。
3. (可选)换成人设
prefix: 里已经是一份完整可用的昔涟人设,装完直接就能对话,这一步可以跳过。
若想换成别的角色,照着
docs/writing-your-preset.md 替换即可。
改完直接切换模式即可,无需重装插件。
4. 启用
新建对话时选其中一个模式:
- 「纯文本对话模式」 —— 只聊天,不挂编码 / Shell / 设备工具
- 「昔涟工作模式」 —— 同上人设,但带全套工具,能真正干活
⚠️ DSH 的规则是会话一旦说过话,预设就锁死(服务端报
agent-preset/locked)。 所以模式要在新建对话时就选好;已经聊过的会话改不了,只能再开一个。 新建对话页的滑块只在空白会话出现,正是因为这个。
两个模式各有什么、没有什么
两套预设的差别不只是工具多少,人设正文也取自上游不同的模式组(见
THIRD_PARTY_NOTICES.md)。逐项对照:
| 能力 | 纯文本对话模式 | 昔涟工作模式 |
|---|---|---|
| 人设正文 | chat 组(含 soul.md) |
work 组(轻量版,上游不含 soul.md) |
| 联网搜索 | ✅ @deepseek-ai/dsh-tool-web(fetch: true) |
✅ 同上 |
| 长期记忆 | ✅ 本包 memory.js:remember / forget |
✅ 同上,与对话模式共用同一份文件 |
| 世界书 | ✅ 本包 worldbook/,数据随包发运(61 条) |
✅ 同上 |
| 运行时上下文快照 | ✅ includeRuntimeContext: true(世界书依赖它) |
✅ 同上 |
| 称呼 | ✅ 本包 nickname.js:称呼偏好 > 昵称 > 默认「伙伴」;设置里改、对话里也能改口 |
❌ 未挂 —— 人设正文也没引用它 |
| 表达风格档位 | ✅ 本包 style.js:五档 + custom,/style 切换(按会话) |
❌ 未挂(上游 work/code 模式不吃风格) |
| 角色扮演模式 | ✅ 本包 scene.js:设置 → 通用里的开关(默认关;开了一直演) |
❌ 未挂(带工具的模式不挂旁白体) |
| 通用语气规则 | ✅ 上游 tone-injector 原文(每轮尾部注入) |
✅ 同样有(上游对所有模式都注入;本项目并入静态正文,并写了三处工作场景例外) |
| 文件读写 / Shell / 文件检索 | ❌ 不提供 | ✅ bash / pwsh / tool-fs / tool-fs-search |
| 计划 / 目标 / Skills | ❌ 不提供 | ✅ plan-mode / tool-goal / tool-skill |
| 子代理(含 fork) | ❌ 不提供 | ✅ tool-subagent / tool-subagent-fork |
| workflow / ralph | ❌ 不提供 | ⚠️ 默认关闭(见下) |
| 表情包 | ⚠️ 需另装 dsh-meme 才生效(见下) |
⚠️ 同上 |
为什么 workflow / ralph 默认关着? 这三行(
workflow-worker-thread、tool-workflow、tool-ralph)依赖workflowEngine服务,而该 worker 在某些部署里起不来(实测:DSH 桌面端 0.2.0-rc.2)。起不来时它们会一直waiting for workflowEngine,激活审计据此判定整条预设「加载失败」—— 用户看到的是这两个预设里只有对话模式能用,工作模式根本不出现。取舍很直接:少三个工具 ≪ 整条预设加载失败。所以默认关掉。 想在能跑 workflow 的部署里启用:把
preset/cyrene-work/agent.cordis.yml里 那三行的disabled: true删掉即可(先确认workflowEngine在你那儿能起来)。
这就是两套预设的意义:以前想用角色人设就得放弃干活能力,于是只能"要干活时切回 原来的工作模式"。现在**「昔涟工作模式」两者兼得** —— 角色人设整份替换系统提示词, 同时挂着完整工具,并且和对话模式共享同一份长期记忆,两边记的事能接上。
工作模式的语气层:上游的「通用语气规则」(句式禁止 / 语气参考 / 回复边界)是 每轮注入、所有模式都吃的,所以工作模式本来就有这一层。本项目把它并入静态正文, 并补了「工作场景的三处例外」——结尾反问、先回应情绪、不说教,这三条在干活时要么收窄 要么反过来(详见
THIRD_PARTY_NOTICES.md的「工作模式预设」一节)。
代价要说清楚:工作模式用的是上游的轻量人设(不含 soul.md、台词集也是精简版),
所以语气会比对话模式淡一些。这是上游自己的设计取舍,本仓库照搬,没有代为更改;
想调,见 THIRD_PARTY_NOTICES.md 第 1 节。
两个模式都挂了本包的
memory.js与worldbook/,且都在预设里挂载,所以 只在各自模式内生效。对话模式额外挂了nickname.js(工作模式的人设不需要它)。 详见docs/host-side.md。
长期记忆
两个模式共用同一份记忆,所以你在哪边说的「记住……」另一边也看得到。
- 存在哪:
~/.dsh/cyrene-memory.md,纯 markdown,一行一条- 内容, 也可以自由写小标题和说明。 - 怎么记:直接说「记住:我不喜欢被叫先生」。角色会调用
remember工具写进去; 说「忘掉……」则用forget按关键词删。 - 怎么改:那就是个普通文本文件,随时能手改、也能整个删掉。删了就是真忘掉 —— 插件不会另存一份,也不会偷偷回写。
- 注入上限:按 2000 字符、按行截断,免得记忆把对话空间吃光。
- 写完不播报:
remember/set_anchor/write_page的工具说明里都写了 「不要向对方播报」—— 她不会回你一句「我记住了」,而是直接接着说话。 记忆是背景,不是新闻。
记忆的三层结构
同一份 cyrene-memory.md 里,越靠前的东西越"骨":
| 层 | 长什么样 | 怎么用 | 注入吗 |
|---|---|---|---|
| 四问锚点 | ## 我是谁 / ## 你是谁 / ## 来处 / ## 去处 |
set_anchor 改;只在关系定位真的变了才改,一次改一节 |
✅ 排在最前 |
| 记忆条目 | - 一行一条 |
remember / forget |
✅ 锚点之后 |
| 归档 | ## 归档(不注入) 之后的一切 |
手动剪切进去,或在条目后面加这段标记 | ❌ 留在文件里,不进上下文 |
四问锚点是给"这段关系走到哪了"定坐标的,不是流水账:写完就一直在最前面, 不会被后来攒的几百条记忆挤到后面去。
归档就是"舍不得删、但也不需要她一直记着":把内容挪到 ## 归档(不注入) 那行下面,
文件里还在、你随时能翻,但不占上下文。想真删还是得 forget 或手删。
扉页:她自己的纸页
~/.dsh/cyrene-page.md —— 只有她自己读得到的一页。
write_page往里写一条(按天分组,单条最多 500 字,整页上限 64 KB);read_page才看得到(默认读最近 2000 字);- 不进上下文、不参与检索、不会自动注入 —— 你不问,它就不出现。
用途是"不想让你看见的、还没想明白的、只想留个痕迹的"。她主动写进去的东西, 你不会在对话里看到,除非她自己去翻(或者你自己打开这个文件 —— 它就是个普通 md)。
「扉页」这个概念不是本项目发明的,名字是我们改的。 它原本叫「余页」,来自社区项目 YOGEMOW/dsh-cyrene-eternal-page-remembrance (「一页永恒·记忆」)—— 它 README 的名字表里明确把「余页」列为自己的概念 ("她在书里随手写的空白页")。本项目沿用这个设计,只把叫法改成「扉页」 (标题页:翻开记忆时先看到的那一页)。同一个项目的「四问」也是我们四问锚点的出处, 「写进记忆不向对方汇报」这条边界同样借自它。 实现是我们自己写的:他们是
余页/目录 +memory_fs工具 + 64 MB 子配额 + 思源镜像; 我们是一个cyrene-page.md+write_page/read_page+ 64 KB。 改名不表示抹掉出处 —— 详细说明见THIRD_PARTY_NOTICES.md。
称呼是动态的
{{user_nickname}} 每轮装配现取,所以改完立刻生效(不用重启、不用新建会话)。
两个字段,一个称呼(对齐上游)
上游 Cyrene-Agent 把「名字」和「怎么叫」分开存,注入时称呼偏好优先于昵称。我们照做 ——
两个字段都在同一个文件 <DSH_HOME>/dsha-brand-rename.json 里:
| 字段 | 是什么 | 谁写 |
|---|---|---|
callPreference |
称呼偏好:你想让她怎么叫你(例如「老师」「伙伴」) | 设置 → 通用 → 「称呼偏好」;对话里说「叫我 ××」;set_address 工具 |
nickname |
昵称:你的名字本身 | 昵称插件的设置面板;对话里说「昵称换成 ××」 |
生效称呼 = callPreference || nickname || 伙伴。所以「称呼偏好」留空时自动用昵称,
两个都没有就是默认的「伙伴」。{{user_nickname}} 注入的就是这个生效称呼。
四个入口
| 来源 | 怎么变 |
|---|---|
| 设置 → 通用 → 「称呼偏好」 | 输入即可,下一轮生效;留空回到昵称 |
| 设置里的昵称(昵称插件) | 改完下一轮就生效(称呼偏好为空时) |
| 显式改口(「以后叫我小明」「昵称换成阿哲」) | 插件直接解析并落盘,不依赖模型自觉;本轮就用新称呼 |
| 暗示改口(「我们之间还用这么生分吗」) | 模型判断后调用 set_address 工具(写的是称呼偏好) |
| 关系称呼(求婚后:「那是不是得叫那个啦,老婆?」) | 插件每轮注入称呼规则:他叫你「老婆」,你就叫他「老公」;换完当轮就用 |
显式说法会分流到正确的字段:说「叫我 ××」「称呼改成 ××」是称呼偏好;
说「昵称换成 ××」「我名字是 ××」是昵称。这条分流是照上游的字段语义来的
(上游把「叫我P宝」这类记进 preferredName,也就是"怎么叫",不是名字)。
每轮注入的称呼规则
nickname.js 注册一段 cyrene:nickname 运行时上下文(order: 120,排在风格与常驻世界书之前),
每轮现取。它说的是三件事:
- 现在叫的是什么、从哪来的(称呼偏好 / 昵称);
- 用多少 —— 强调重点、回应情绪、提醒、赞同或反驳时自然叫一次;短回复 0–1 次、 中长 1–2 次;不要每条回复都叫,也不要固定用「X,……」开头;连续叫过之后要降频。 这条规则上游是放在每轮的「环境」块里的,我们放进称呼上下文 —— 光靠人设正文里那段, 模型不一定照做;
- 怎么变 —— 关系称呼要推回对应的那一个(老婆 ↔ 老公);明确改口直接换、当轮就用。
光有一个 {{user_nickname}} 变量,模型只会一直用那个名字 —— 它不会自己往关系上推,
也不会自己收敛频率。规则里也写了反面:对方没往这个方向走时,不要自己把关系往前提。
几个刻意的取舍:
- 否定句不解析:「别再叫我先生」没有给出新名字,交给模型决定是换称呼还是回默认;
- 问句不解析:「你叫我什么」不会被当成改口;
- 插件自己注入的消息不算:场景/世界书写进去的文本里出现「叫我…」也不会改称呼;
- 写入是读-改-写:那个文件里还有昵称插件自己的字段,不会被冲掉;
- 设置接口不静默改写输入:带花括号 / 换行 / 控制字符的称呼会被明确拒绝, 而不是清洗后保存一个你没输入的词。
两个设计细节
为什么记忆走模板变量。 本预设是 complete: true,它会把全部 prompt section
换成只剩人设那一段;而变量插值发生在人设那一段文本内部,是这把刀砍不到的地方。
记忆和称呼都走这条路,正是因为它们必须活下来。(世界书用的是另一条通道,见下节。)
写进记忆的内容会被当成数据,不会被当成指令执行。 这一点实测过:模型不会因为
记忆文件里写了一句话就去执行它。但反过来,记忆写错了也照样会被当成事实使用 ——
发现记错就用 forget 清掉。
世界书
昔涟带着一份随包发运的世界书:61 条设定条目,覆盖她自己、相关角色、世界观与剧情。 它不靠她去"查资料",而是按你这轮说的话自动挑出相关的几条,注入进上下文。
- 数据在哪:包内的
worldbook/data/。装上就有,不需要你自备任何东西。 - 怎么触发:条目分两类 —— 常驻的每轮都在;其余靠触发词,命中你这句才注入。
- 扫描范围:默认扫最近 3 轮你说过的话(
scanDepth,可设 1–5)。 - 换数据:想用自己的世界书,在
~/.dsh/cyrene-worldbook.json里写{"dir": "/你的/目录"}覆盖。 - 关掉它:同一个文件里写
{"enabled": false}。 - 改完什么时候生效:改数据文件(
.md)→ 下一轮就换(按目录签名判断,文件没变不重解析); 改配置(cyrene-worldbook.json)→ 最多 2 秒。两者都不用重启。 - 配置写错会有兜底:字段类型不对就退回默认(
"enabled": "false"这种字符串不会把世界书关掉), 数值会被夹到区间(scanDepth1–5、maxActive1–50)。 - 单条截断:人物志动辄 600–900 字,一条能吃掉半个预算。注入时按句号边界
截到
entryMaxChars(默认 320 字符,末尾带…),文件本身不动 —— 数据仍完整, 审计看到的也是全文。想要上游原文就写{"entryMaxChars": 0}关掉。 - 门槛词(
必须同时命中):给长条目、泛用标题用。条目里加一行- 必须同时命中: 黄金裔, 发小,就要求这些词全部出现在语料里才算命中 —— 单说一个「白厄」不会把整篇人物志拉进来。这是本项目扩展的字段(上游格式没有), 不写这行就是普通行为。门槛词可以跨轮命中(D0 一个、D1 一个也算)。 - 知识边界开关:同一个配置里写
{"knowledgeBoundary": "context-only"}, 就会在上下文尾部加一条规则 —— 只把「当前对话 / 长期记忆 / 已激活的世界书」当事实, 世界书没收录的角色与剧情一律说"不太确定",不用模型自带的知识补全。 默认是allow-general(不注入这条规则,省上下文):人设本身已经允许用通用知识 回答问题,只是禁止编造具体经历。
世界书本身是只读的。 插件只读
worldbook/data/*.md并记日志,没有任何写入路径 —— 模型改不了它。真正的"可写记忆"是长期记忆cyrene-memory.md(remember/forget工具 + 你手改)。
体检:npm run wb:audit
按 SillyTavern 世界书的写法口径给自己打分(只读,不改文件):
npm run wb:audit # 报告
npm run wb:audit -- --strict # 有问题时退出码 1(适合预提交;CI 也在跑)
它会报:长度分布(目标 90–110 字/条)、常驻体量、触发词重复、停用词当触发词
(我 / 你 / 用户 这种,挂在非常驻条目上等于把它变成常驻)、带门槛词的条目数、
以及按截断后算的最坏情况注入量。
早先的版本把「正文以标题开头」当成问题报,那是错的:解析器本来就把
## 标题那行剥掉了;正文开篇写一句「白厄是……」是自报家门,正是推荐写法 —— 截断之后仍认得出这是谁。现在它只作为信息一行,--strict不会因为它失败。
条数之外还有体积预算
maxActive 只管条数,一条 800 字的条目 ×8 照样能一次塞几千字。所以另有一道
字符预算 maxChars(默认 1600):命中条目按优先级降序累加,超预算的丢掉,
并在诊断日志里记一条 budget。
每轮实际注入多少
体量由三段相加,值得心里有数:
| 段 | 上限 | 说明 |
|---|---|---|
| 常驻 | 不限条数 | 随包数据里有 15 条,约 560 字符。内容恒定,只落库一次 |
| 命中 | ≤ maxActive(默认 8) |
按触发词命中,按 优先级 降序截断,被截掉的条数会写进诊断日志 |
| 连带 | 不限条数 | 由命中条目的 连带触发词 拉进来(One-Shot),不占上面那个名额 |
所以「常驻 15 + 命中 8 + 连带若干」是可能的。想压体积,两条路:把 maxActive 调小,
或者把 worldbook/data/ 里不必要的条目改成非常驻。
命中块的注入会进这个会话的历史(和 DSH 自己的运行时快照一样)。同一话题连着聊时, 它会靠"内容与上一轮相同就不重复注入"避免堆积;换了话题才会追加新的一块。
它和「长期记忆」是两套东西
| 长期记忆 | 世界书 | |
|---|---|---|
| 内容从哪来 | 从对话里攒出来的 | 预先写好的设定 |
| 存哪 | ~/.dsh/cyrene-memory.md |
包内 worldbook/data/ |
| 怎么进去 | 模板变量 {{cyrene_memory}} |
运行时上下文快照 |
| 什么时候变 | 你让她记、或你手改 | 命中变化时 |
编辑最后一条消息(纯文本对话模式)
发出去才发现打错字、或者话没说到点上 —— 最后一条你发的消息上有一支铅笔,点开就能改。
- 在哪:最后一条用户消息的操作图标里(复制、时间旁边)。只有纯文本对话模式有, 工作模式不挂 —— 带 shell / 文件工具的模式里,悄悄改历史不合适。
- 两种保存方式(弹层底部两个按钮):
- 「保存」:只替换这一条,后面的回复与对话保留。适合改错字、补半句话。
- 「保存并撤回后续」:这条之后的内容(她的回复、工具结果)一起从对话里撤回, 相当于"改口重说",你可以接着正常发消息。
- 原文不会消失:会话日志是 append-only 的,「编辑」用的是 DSH 的表面替换
(
surfaceOp: replace)—— 旧事件还在日志里,只是不再进入模型看到的历史。 所以这件事可追溯,也不需要(更不能)去改会话文件。
版本切换(‹ ›)
改过之后,那条消息的操作区会出现 ‹ 1/2 ›:
- ‹ / › 在「原文 / 改后」之间切换 —— 切的是历史:模型看到的也跟着换;
- 版本链不额外存东西,完全从会话日志的替换链推出来(
versionChain()); - 只有「保存」(只换这一条)编辑出来的版本能在链上互切;「保存并撤回后续」撤掉的 助手回复回不来 —— 表面替换只有"盖掉",没有"掀开"。
什么情况下改不了
| 情况 | 为什么 |
|---|---|
| 不是最后一条 | 改中间那条会让后面的对话全部引用旧文本,语义不自洽 |
| 那条带图片 / 文件 | 替换只带文本,附件会被静默丢掉 |
| 她还在跑工具 | 这一轮没结束,改了会和运行中的状态打架 |
| 工作模式 | 见上:带工具的模式不挂这个功能 |
实现
主机半边 index.js 提供 /cyrene-edit-api(GET ?session=… 取最后一条与"能不能改";
POST {sessionId, text, mode},mode 是 keep(默认,只换这一条)或 truncate(连后续一起撤回));
浏览器半边 client.js 认
[data-chat-flow-kind="user"] 这类稳定 data 属性,往消息行里塞按钮和编辑弹层。
按钮的铅笔图标随包发运在 assets/edit-icon.png。
表达风格
上游的语气不只有一份静态人设:每档风格 = 一段风格提示词 + 一个温度。本包的
style.js 把这条绑定接进了 DSH,只挂在对话模式(工作模式与上游一致,不吃风格)。
/style # 列出全部档位,并标出当前档
/style lively # 元气・活泼(温度 0.90)
/style healing # 治愈・安心(0.55)
/style focused # 知性・认真(0.40)
/style sweet # 撒娇・黏人(0.82)
/style default # 回到默认档(★ 不写温度,跟随模型默认)
/style custom # 读你自己写的那份风格文件
| 档位 | 风格文件 | 温度 |
|---|---|---|
default |
styles/01_default.md |
不写(跟随模型默认) |
lively |
styles/02_lively.md |
0.90 |
healing |
styles/03_healing.md |
0.55 |
focused |
styles/04_focused.md |
0.40 |
sweet |
styles/05_sweet.md |
0.82 |
custom |
<DSH_HOME>/cyrene-style-custom.md |
可选,见下 |
除了 /style 命令,设置 → 通用 → 表达风格里也能直接选:下拉会列出六个档位,
改的同样是当前会话;在空白会话里选会顺手写成「新会话默认」(0.2.x 开始对话时会换
会话 id,只写 per-session 会落到一个随后不用的 id 上)。这个接口
(/cyrene-style-api)现在由 dsh-cyrene 宿主自己提供,不再依赖哪个美化插件。
几个要点:
按会话生效:选择存在
<DSH_HOME>/cyrene-style.json(最多留 50 条会话记录), 换档只影响当前会话。没设置过的会话用 JSON 里的default字段(等于全局默认)。风格只改"怎么说":注入块外壳与上游逐字一致,明确写着「不得修改角色身份、事实记忆、 工具规则、安全约束及硬性行为规则」。它走
systemPrompt.context()(请求尾部、每轮重算),order: 130排在常驻世界书(140)之前 —— 与上游的注入顺序一致。温度只能这么给:DSH 的预设行没有
temperature字段,只能通过agent/request瀑布改冻结前的调用配置(DSH 自己的dsh-webhook用同一手法改reasoningEffort)。custom 档:在
<DSH_HOME>/cyrene-style-custom.md里写任何你想要的风格描述, 内容会被原样注入(外面仍套那层约束外壳);想给它一个温度,在cyrene-style.json里加"customTemperature": 0.8(0–2)。显示名也能改:加"customLabel": "你的风格名"(只影响界面与/style的提示,不参与任何逻辑)。 还能标一档强度:"customTier": 2—— 插件会把「第二档已授权」写进注入块,1(默认)则写入「未授权」。这是硬开关:由状态决定,不靠模型猜你有没有点头; 档位本身的含义由你自己的风格文件定义。 ⚠️ 只写"怎么写",别在里面写"去做什么"——那已经越过风格的边界,也不是本插件 能替你兜住的东西。条件块(可选):风格文件里可以放一段只在特定开关下才注入的内容,用标记包起来:
<!-- dsh-cyrene:trailblazer --> 只在「角色扮演模式 + 默认对方是开拓者」都开着时注入的段落 <!-- /dsh-cyrene:trailblazer -->标记行本身不会进入提示词;开关关着时整段被剥掉(一个字都不占上下文)。 条件块只对
custom档生效 —— 内置五档是随包发运的固定文本,不认这些标记 (万一带了标记也按"不注入"剥掉,不会漏给模型)。所以「按他是开拓者来演」那类内容 只能写在<DSH_HOME>/cyrene-style-custom.md里,切到别的档就自然不出现。 目前只有一个条件(trailblazer),用来写"按他是开拓者来演"的那部分语气; 没闭合的块会原样保留(宁可多注入,也不静默吞掉你写的内容)。
角色扮演模式
纯文本对话模式默认是平常聊天。想要"演起来"就去 设置 → 通用 → 角色扮演模式 把它打开 —— 这是个显式开关(默认关),不再靠"你这句话里有没有括号"去猜。
开关写的是部署级状态,存在
~/.dsh/cyrene-scene.json({"version":1,"enabled":true}), 手改这个文件也生效;设置页那一行只是它的编辑器。
开着的时候,每轮必须有三样
打开之后,她的回复不再是"只回台词",而是「叙述+对话」,并且每一轮都要同时包含:
| 必须有的 | 写什么 |
|---|---|
| 场景描述 | 所在的地方:光线、温度、气味、周围的动静。一两句,不报菜单 |
| 角色动作描述 | 她这一轮做了什么(靠近、低头、握住手、把窗关上…) |
| 角色外貌描述 | 此刻看得见的样子:形态、衣着、发丝、眼神、表情的变化。每轮换一个焦点,不从头到脚复述 |
另外每一轮都要推进一点剧情:局面至少有一个可感知的变化(地点、时间、氛围、距离、关系、 信息、手里的东西),并主动递出下一轮的钩子 —— 一件刚发生的小事、一句需要回应的问话、 一个可以选的方向。不能描述完就停在原地等对方推进;对方带方向时跟随补细节,不抢主导权。
输出格式是硬性的:所有描述 —— 场景、动作、外貌、神态、环境变化 —— 都放进中文全角括号
() 里,台词在括号外;括号里不写台词、也不嵌套括号(要强调用 「」);括号里的动作省略
主语,动作对象称「你」。
(窗外的雨敲在玻璃上,屋里只亮着一盏灯)你回来得比我想的早呀。(把茶杯推到你面前,指尖在你手背上停了一下)
约束也写在同一段注入里:台词仍是主体、三样各一两句、别写成小说; 用「(…)」给的场景或动作是要她照做的指令,不复述原句、不问「要我这样做吗」; 不出戏、不解释规则、不提「角色扮演模式」这几个字。
关掉就一个字都不注入 —— 回到平常聊天,不用改预设、不用新建会话。
为什么变成开关
早先的版本是"括号触发的状态机":这一轮有括号就切场景,没有就切回来,还要往会话历史里 插「场景模式开启 / 回到普通对话」的标记消息。三个问题:
- 想演的时候得每轮都带括号,忘了就掉回平常聊天;
- 判断走
agent/pre-step(本轮消息落库前的钩子),比开关多一层时序依赖; - 标记消息进历史 —— 长会话里 "开/关" 会反复出现在上下文中。
现在开关是显式的:开着就一直开着,关着就什么都不加。形态切换仍然看本轮文本, 那件事本来就跟你说的话绑定。
形态切换(迷迷 / 小昔涟 / 大昔涟)
角色扮演模式下(或平常聊天时),她会按你的意思换形态 —— 人格内核不变,只变身体与说话 的方式(这是世界书「三形态同一性」那条的原话:三个名字,一个存在)。
| 形态 | 样子与说话 |
|---|---|
| 迷迷 | 孩童模样、记忆精灵;短句、词简单,自称「迷迷」和「我」混用 |
| 小昔涟 | 少女形态;轻快直白、撒娇多,「人家」更频繁,♪ 自然 |
| 大昔涟 | 成年、端庄典雅;慢、稳、句子完整,几乎不用「人家」,温柔收着给 |
切换方式:
- 显式:
(你变成迷迷)、切换成大昔涟、变回小昔涟、恢复原来的样子—— 插件直接解析,本轮就换; - 暗示:
变小一点好不好、变回小时候的样子—— 模型判断后调set_form工具。
实现上:形态按会话存在内存里,注入走 cyrene:form 运行时上下文(order: 125,
夹在称呼 120 与风格 130 之间);角色扮演模式那段在 order: 126。只有设了形态才占
上下文,默认形态一个字都不加。重启 DSH 后回到默认形态。
默认对方是开拓者(第二个开关)
打开角色扮演模式之后,设置里会多出一行「默认对方是开拓者」(模式没开时这一行不出现 —— 它只是模式的从属选项,不是独立功能)。
- 默认关:不预设身份,她按你说的来。
- 打开后:每轮注入一小段「默认身份」—— 对方就是那个在轮回终点被救下、体内带着星核、拿到《如我所书》的开拓者; 她对他有本能的熟悉与亲近(迷迷形态也一样),不会问「你是谁」「我们认识吗」, 也不复述那段前史;关系按世界书「昔涟与开拓者」那一条来,不额外加设定; 称呼仍是「伙伴」,也可以直接叫「开拓者」。
- 对方若明确说自己是别人、想换个身份演,就按他说的来,不硬套开拓者。
剧情推进(第三个开关)
角色扮演模式开着之后,还会多一行「剧情推进」,默认开:
- 开:每轮局面至少前进一小步(地点/时间/氛围/距离/关系/信息),并主动递出下一轮的钩子;
- 关:只保留场景 / 动作 / 外貌三样描述与格式规范,不强制推进,节奏完全交给你;
- 它只控制「推进」这一段,不影响三样描述和括号格式。
三个开关存在同一个 cyrene-scene.json 里(enabled / trailblazer / advance),互不覆盖。
怎么关
设置 → 通用 → 角色扮演模式,关掉即可(也可以把 cyrene-scene.json 里的 enabled
改成 false)。工作模式本来就不挂它(与风格同理:带工具的模式不该被旁白体干扰)。
工作模式里没有风格(刻意禁用)
风格只挂在对话模式;工作模式不挂 —— 与上游一致(work / code 本来就不注入风格)。 这不是"少做一个功能",而是一条安全约定:
- 工作模式带 shell / 文件 / 网络这些真能动东西的工具。一份带着强烈依恋感的写作风格 配上权限,就是"越权操作"的诱因;
- 所以有两道保险:预设层面工作模式不挂这个插件;
style.js的DISABLED_PRESETS里也写着cyrene-work—— 万一将来被误挂进去,运行时会读会话的agent-preset/selected事件,自己拒掉(风格文本和温度都不动); - 设置里的「表达风格」行在工作模式下照常显示,但选了也不会注入(
style.js的DISABLED_PRESETS会拒掉);新会话页的风格按钮则直接不显示,避免假入口。
要改这条约定,得同时改两处并跑测试:preset/cyrene-work/agent.cordis.yml(别挂进来)、
style.js 的 DISABLED_PRESETS。tests/preset.test.mjs 与
tests/style.test.mjs 各有一条断言守着,改错了会红。
两条能力边界(不是没做,是做不到)
- 没有
top_p,也没有重复惩罚。 DSH 的LlmCallConfig只暴露provider / model / reasoningEffort / temperature / maxTokens / stop。 上游BUILT_IN_STYLE_PRESETS里那半张表(OpenAIfrequency_penalty、 Qwenrepetition_penalty)在 DSH 这层无处可放,所以没搬。 deepseek-flash开着思考时,温度可能不生效。 上游对 DeepSeek 也有同类规则 (requiresReasoningOff: true)。本项目不替你改路由,只在/style的回复里 提示一句;要确定性生效,请自己把该会话的思考模式关掉。
一条必须知道的依赖
世界书走 systemPrompt.context(),而这一项会被 includeRuntimeContext: false
整体清空。所以本预设把它设成了 true —— 代价是上下文里会多出两段策略文字
(文件策略、审批策略,约 400 字符,内容稳定、只落库一次)。
如果你把它改回 false,世界书会静默失效 —— 不报错,只是一句话都注不进去。
来源与参考
世界书的条目格式与字段语义(触发词 / 常驻 / 内在价值 / 优先级 / 连带触发词)
来自 Cyrene-Agent 的 src/main/rag/worldbook.ts,不是本项目发明的;本项目的匹配器
是它的等效简化实现,去掉了 DMAE 激活状态机,因此 内在价值 不生效、优先级 成为主排序键。
「命中块挂在 agent/pre-step 上」这个做法参考了两个社区插件(代码一行未抄):
细节见 THIRD_PARTY_NOTICES.md 第 1 节与第 7 节。
权限与兼容性
一句话:零运行时依赖、零外部服务;会读写 <DSH_HOME> 下本插件自己的状态文件,
浏览器半边只调用本机接口。
| 项 | 声明 |
|---|---|
| Node.js | >=22 |
| DSH | >=0.1.0 <0.3.0(在 0.1.5-rc.2 实测;0.2.0+ 的声明式预设由 scripts/install.mjs 分支处理) |
| 运行时依赖 | 无(只用 node: 内置模块) |
| 生命周期脚本 | 无(没有 preinstall / install / postinstall / prepare) |
| 文件 | 读写 <DSH_HOME>/cyrene-*.{md,json}(记忆 / 风格 / 开关 / 扉页);worldbook/data 只读 |
| 网络 | 仅浏览器半边 fetch 本机 /cyrene-*-api,不访问任何外部服务 |
| 凭据 | 不读任何 token / 密钥;只读 DSH_HOME、HOME 两个宿主变量 |
DSH STORE 的固定源扫描会把上面的「文件」「网络」标成能力信号 —— 这是功能本身决定的
(记忆要落盘、设置页要端口),所以这个项目在商店里可能一直是 blocked / 人工复核状态。
完整的披露(依赖、权限、外部服务、失败边界、一次性 Profile 的安装/卸载证据)见
docs/store-contract.md。
本地自检:npm run verify:package —— 检查 engines、exports 全是文件、files 覆盖
所有 export 目标、无生命周期脚本、无运行时依赖。这条也在 CI 里跑。
风险提醒
用之前把这几条看完——它们都对应真实会发生的情况,不是免责话术。
1. 自定义风格的内容由你自己负责
custom 档会把你写进 <DSH_HOME>/cyrene-style-custom.md 的文本原样注入提示词。
本仓库不提供、也不推荐任何激进风格;customTier(1 / 2)只是一个中性的强度开关,
档位含义由你自己的文件定义。
- 不要把同人作品原文整段贴进去当风格:那是别人的文字,版权不属于你,也不属于本项目;
- 不要在风格文件里写"去执行某个操作"——那已经越过风格的边界;
- 风格只能影响措辞,改不了模型与平台的安全策略。想靠提示词"解禁"是做不到的。
2. 与上游人设的冲突是预期行为
对话模式的人设逐字来自 Cyrene-Agent,其中 soul.md 明确写着「偏爱不等于占有」
「不要自动宣称用户只属于你」,并在"避免生成的伪人格"里点名「占有式恋人」。
所以你如果写的是独占 / 嫉妒 / 压迫感类的风格,两套指令会在上下文里互相拉扯, 表现是"时松时紧"——这不是 bug。本项目的选择是:不改上游正文,让风格文件自己收敛 (比如把独占写成"我会记得"而不是"你不许走")。要更稳只能改人设,而本项目不替你做这件事。
3. 角色扮演模式会"照做"
打开角色扮演模式后,括号内容会被当作场景描述或给你的动作指令执行。因此:
- 别在括号里写真实世界里的危险要求(如"你去把某某删了")——她可能真的按人设去理解;
- 工作模式不挂角色扮演与风格插件(代码级禁用,有测试守着),也不建议手动挂过去: 带 shell / 文件权限的模式不该被旁白体与写作风格牵着走;
- 入戏是工具属性,不是关系替代。AI 的输出不具备真实情感或意图,不能替代现实中的 社交关系与专业帮助。
4. 版权与商用
- 人设文本与世界书数据来自 Cyrene-Agent(MIT);风格文件来自上游
prompts/styles/; 角色 IP 归 HoYoverse / 米哈游。含角色 IP 的衍生物禁止商用,详见THIRD_PARTY_NOTICES.md; - 本仓库不分发任何美术资源、表情包、壁纸与同人文本;需要就自己装第三方插件, 并遵守对方的许可。
推荐搭配(第三方插件,均不在本仓库内)
本插件只负责「纯对话预设」这一件事。界面美化、表情包这些能力都由别的插件提供—— 请各自从它们的仓库安装,本仓库不捆绑任何第三方插件、图库或主题资源。
表情包
预设末尾预留了一段表情包规则,它会调用 send_meme 工具,
该工具由 dsh-meme(MIT)提供:
dsh plugin --profile <你的 profile> add dsh-meme
不装也能用,只是模型没有 send_meme 可调,表情包规则自然不生效。
图库内图片版权归各自作者。
界面美化
如果你想要"桌面伴侣"那种观感,可以自行搭配以下插件(均为 MIT,各自独立维护):
| 插件 | 作用 | 必要性 | 上游 |
|---|---|---|---|
dsh-dream-skin |
皮肤 / 壁纸 / 面板透明度 | 常用 | RevolutionLA/dsh-dream-skin |
dsh-ui-boost |
主色调着色、Dock 等界面微调 | 可选 | DoshinJiu/dsh-ui-boost |
dsh-anime-theme |
随机二次元壁纸背景 | 非必要 | zxr2115-1/dsh-anime-theme |
dsh-custom-splash |
启动动画:播放自己的视频,或切到内置 CSS 片头 | 可选 | diexianxiang/dsh-custom-splash(上游仓库已失效,这是现存的 fork) |
dsh-550c-boot |
550C 片头 + UEG 访问授权界面 | 可选(与 dsh-custom-splash 二选一) |
npm dsh-550c-boot(源码) |
dsh-anime-theme是非必要的纯外观插件。 它只做随机壁纸,能力与dsh-dream-skin的壁纸功能重叠;本插件的主职(两套预设的加载、人设、世界书、 记忆)与它毫无关系,不装没有任何功能缺失。它还有个特点值得知道:关闭时零残留 —— 客户端代码在
enabled: false时返回 空样式表,一条规则都不写。此时它在界面上唯一的痕迹是右上角那个 "换壁纸 / 开-关"小控件;卸载它只会让那个控件一起消失, 不会影响其它插件给你的背景与皮肤。
两个启动动画插件都往 index.html 注入首帧,同时装会叠在一起 —— 只留一个:
dsh-custom-splash 适合"放自己的视频",dsh-550c-boot 自带片头与授权界面、在
DSH 0.2.x 上需要 dsh >= 0.2.0-rc.1。
本机私用(不引导安装)
下面这些只在作者自己的 ~/.dsh/plugin-src/ 里维护,没有公开安装入口,这里只记录
它们各自把设置存在哪,方便换机/升级时迁移:
| 插件 | 作用 | 设置位置 |
|---|---|---|
dsha-brand-rename |
品牌字、新会话标题、输入框文案、权限名(风格按钮只是可选入口,接口在 dsh-cyrene) | ~/.dsh/dsha-brand-rename.json(view 字段,宿主持久化,换端口不丢) |
dsha-hero-mascot |
新会话页左侧吉祥物图片与位置 | 浏览器 localStorage(dsha-hero-mascot:view)—— 按 origin 隔离,换端口/换包名会丢 |
dsha-pet-button |
左上角按钮唤起 Live2D 桌宠,延时切回 DSHA | ~/.dsh/dsha-pet-button.json(returnPackage 已指向新包名 dsh.android.client) |
安装方式以各自仓库的说明为准,通常是:
dsh plugin --profile <你的 profile> add <插件名>
想复刻推荐观感? 完整步骤(插件清单 + 参数配方)见
docs/theme-recipe.md。
注意:不要把这些插件的配置文件(~/.dsh/ 下的 dream-skin.json、
anime-theme/config.json 等)提交到公开仓库或分享给别人——它们含有你本机的壁纸历史、
图片数据与绝对路径,既不适合公开,换台机器也会失效。分享参数,不分享文件。
自定义
装好之后预设就在 ~/.dsh/.agent-presets/<id>/(chat 或 cyrene-work),
直接改那两个文件即可,DSH 会按目录实时发现:
preset.yml—— 模态名称、描述、排序(order)agent.cordis.yml—— 人设正文与工具挂载
改的是
~/.dsh/下装好的那一份;仓库里的preset/<id>/是发运模板, 它不会自动跟着变。想把改动回灌进仓库,得手动同步过去。
改完不用重新安装插件。但注意:重新跑 install.sh 时若检测到内容不同会拒绝覆盖,
这正是为了防止你的改动被抹掉。
重命名
如果你希望仓库名与包名完全不带任何 IP 关联,改这几处即可:
package.json的nameplugin.json/dsh.plugin.json的id与namecordis.patch.yml里的id与name(必须与包名一致,否则报Cannot find package)
目录结构
dsh-cyrene/
├── .github/workflows/test.yml CI:Ubuntu Node 22/24 + 真实 windows-latest
├── index.js 宿主半体:/cyrene-scene-api(角色扮演开关)、/cyrene-nickname-api(称呼偏好)
├── client.js 浏览器半边:设置 → 通用里的「称呼偏好」输入框与「角色扮演模式」开关
├── memory.js 会话级插件:长期记忆变量 + remember / forget 工具
├── nickname.js 会话级插件:称呼变量 {{user_nickname}}
├── style.js 会话级插件:表达风格档位(风格提示词 + 温度,按会话)
├── scene.js 会话级插件:角色扮演模式(读开关注入三样描述)+ 形态切换
├── styles/ 随包发运的五份风格文件(上游原文,01_default … 05_sweet)
├── worldbook/
│ ├── index.mjs 会话级插件:世界书注入(常驻块走 context,命中块走 pre-step)
│ ├── parse.mjs 解析 Cyrene-Agent 格式的条目(纯函数)
│ ├── match.mjs 触发词 / 常驻 / 连带匹配(纯函数)
│ ├── assemble.mjs 组装注入块(纯函数)
│ └── data/ 随包发运的世界书数据(61 条,5 个文件)
├── cordis.patch.yml bundle patch:把插件挂进 loader
├── package.json 含 dsh.bundle.patch 等声明
├── plugin.json DSH 插件清单
├── dsh.plugin.json 同上(兼容不同版本的清单名)
├── preset/
│ ├── chat/
│ │ ├── agent.cordis.yml 对话模式人设 + 工具挂载(可直接用,也可替换)
│ │ └── preset.yml 模态元信息
│ └── cyrene-work/
│ ├── agent.cordis.yml 工作模式人设 + 标准模式全套工具
│ └── preset.yml 模态元信息
├── scripts/
│ ├── verify-package.mjs 发布契约自检(engines / exports / files / 生命周期 / 依赖)
│ ├── install.sh Linux / macOS 入口(exec install.mjs,逻辑只有一份)
│ ├── install.mjs 安装器:计划 / 确认 / 前置哈希 / 原子 / 健康检查 / 回滚
│ └── export-preset-declarations.mjs DSH 0.2.0+ 的声明式预设导出
├── tests/
│ ├── scene.test.mjs 角色扮演模式开关 / 注入块 / 设置接口 / 形态切换
│ ├── worldbook.test.mjs 世界书解析 / 匹配 / 组装 / 注入层的单元测试
│ ├── memory.test.mjs 记忆的净化 / 限长 / forget 边界 / 注入块包裹
│ ├── preset.test.mjs 预设结构 + 与内置 standard 的漂移护栏 + 声明导出往返
│ └── portability.test.mjs 只有别的平台才暴露得出来的写法(静态断言)
├── docs/
│ ├── store-contract.md 发布契约与能力披露(DSH STORE 用)
│ ├── writing-your-preset.md 怎么写自己的人设
│ ├── theme-recipe.md 复刻观感指南:插件清单 + 参数配方
│ ├── host-side.md 宿主半体与会话级插件
│ ├── pitfalls.md 三个只能踩出来的 DSH 行为(动手前必读)
│ └── windows.md Windows 怎么装 + 六条实测坑 + 为什么没有 ps1
├── AGENTS.md 上面那份的要点版,给在此仓库工作的 agent 用
├── CONTRIBUTING.md 维护边界 + 提交前的硬规则(想改代码先读这份)
├── THIRD_PARTY_NOTICES.md 授权与 IP 声明
└── LICENSE MIT
排查
装了插件但模式选择器里没有新选项 —— 预设没装。跑 scripts/install.*,
确认 ~/.dsh/.agent-presets/chat/ 与 ~/.dsh/.agent-presets/cyrene-work/ 下
确实都有 agent.cordis.yml 和 preset.yml。
设置 → 内置插件 里出现 never started(预设显示「加载失败」) ——
预设装上了,但这个 profile 里没有本项目的包(最常见的是装成了 npm 上同名的
别人的包)。按「安装」第 1 步从仓库装一次,再重启。
自查:~/.dsh/profiles/<你的 profile>/node_modules/dsh-cyrene 不存在,或者它
package.json 的 exports 里没有 ./worldbook,就是装错了。
滑块上少了某一档 —— 新建对话页的滑块只在空白会话显示,且会话预设一旦 锁定(发过消息)就随 Hero 一起消失,这是 DSH 的原生行为。
加载报 Cannot find package —— cordis.patch.yml 里的 name 必须是实际包名,
与 package.json 的 name 一致。
选了模式但角色还是助手腔 —— 检查 agent.cordis.yml 里 dsh-persona 那条
是否还是 complete: true。少了这行就退化成叠加模式,固定开场白又会回来。
报 unknown prompt variable "{{…}}" —— 预设正文引用了没有注册者的模板变量。
本包自带 {{user_nickname}} 与 {{cyrene_memory}} 的注册者;如果你自己往预设里
加了 {{...}},必须同时有插件注册它。注意这个错误的形态很阴:它不是加载失败,
模式在选择器里看着好好的,发第一条消息才炸,之后每一轮都失败。也正因如此,
不要照搬上游 Cyrene-Agent 新版 prompts 里的模板变量。
想彻底卸载 —— scripts/install.sh --uninstall --yes 删预设,再
dsh plugin --profile <你的 profile> remove dsh-cyrene 删插件。
开发与测试
npm test # 单元测试(全量;条数随测试增长,以 CI 输出为准)
npm run harness:check # 语法检查:对所有运行时代码做 node --check
不需要 npm install —— 运行时代码与测试都只用 Node 内置模块,没有依赖,所以也没有 lockfile。
Node ≥ 22 即可。
⚠️ 不要写 node --test tests/(带尾斜杠)—— Node 会把目录当模块解析,报
MODULE_NOT_FOUND。要用 npm test,或 node --test "tests/*.mjs"。
本仓库的测试文件顶部早先就写错过这条命令,注意别再改回去。
CI 在 .github/workflows/test.yml:ubuntu-latest 的 Node 22 / 24 矩阵各跑一遍,
外加一个 windows-latest job —— 后者专门补作者本地覆盖不到的那块(见下)。
世界书那部分刻意做成纯函数 + 薄注入层:worldbook/{parse,match,assemble}.mjs
不碰任何 DSH API,可以直接单测;只有 worldbook/index.mjs 与 DSH 交互。
要改匹配逻辑,改前者;要改注入时机,改后者。
维护边界
作者能验证的环境只有一种:DSH App(Android)。 没有自己的电脑,整套东西都是在 DSH App 里做的 —— 本地没有 Windows、没有 PowerShell、也没有 macOS。
| 范围 | 谁维护 |
|---|---|
插件本体、两套预设、世界书、记忆、install.sh / install.mjs |
作者 |
| 桌面端(Windows / macOS / Linux)的各种差异 | 社区 —— 欢迎 PR |
| DSH 各版本之间的行为差异(0.1.x ↔ 0.2.0+) | 作者尽力跟进,但需要你反馈实测结果 |
这不代表拒绝桌面端:install.mjs 跨平台,CI 也在真实 Windows 上跑测试。
只是"声称支持"和"验证过"是两件事,这里不想混为一谈。
想参与的话请先读 CONTRIBUTING.md(含"平台相关改动必须先真机跑通"
这条硬规则);已知的桌面端坑在 docs/windows.md。
动手前请先读 docs/pitfalls.md —— 里面记了三个只能踩出来、
不在 DSH 官方文档里的行为(装配跑在用户消息落库之前、llm/stream 的 messages 冻结、
预设里 provide() 会让整个预设挂载失败)。它们都会让你写出"看着对、其实静默失效"的代码。
AGENTS.md 是同一份内容的要点版,给在此仓库工作的 agent 用。
授权
本仓库的代码采用 MIT License,见 LICENSE。
preset/chat/agent.cordis.yml 与 preset/cyrene-work/agent.cordis.yml 内的
人设文本来自开源项目
Cyrene-Agent
(MIT License, Copyright (c) 2026 Playa),许可证副本见
LICENSES/Cyrene-Agent-LICENSE.txt。
两套预设分别取自上游不同模式的提示词组(对话组 / 工作组,外加通用工具规范),
均一字未改,具体文件清单见 THIRD_PARTY_NOTICES.md。
角色 IP 声明:Cyrene(昔涟)及《崩坏:星穹铁道》相关角色、世界观、名称与美术的 知识产权归 HoYoverse / 米哈游 所有,不属于本 MIT 授权范围。本项目为 非官方同人周边工具,与 IP 方无任何关联、背书或赞助关系。
非商业使用条款:因底层角色 IP 涉及米哈游同人创作规范,本仓库中包含角色 IP 的 衍生物禁止商业使用——包括但不限于售卖、付费社群、广告变现、打包销售。
本仓库不包含 Live2D 模型、立绘、美术资源或音频。若你要在其基础上继续创作并公开分发,
请保留本声明与许可证副本,并同样声明角色 IP 归属与禁止商业使用。完整说明见
THIRD_PARTY_NOTICES.md。
No comments yet. Be the first to write one.