前言¶
给 DSH 挂人格,最常见的做法是往 system prompt 里塞一段角色设定,再让模型自己「演」。这类做法的问题是随机性只落在语言层面:同样的输入,模型今天热情明天冷淡,看上去像人格漂移,实质没有约束。下面介绍的插件换了思路——它不写台词,而是给对话对象建一套真实约束:她有日程、有身体周期、有注意力上限,回复的字数、延迟、情绪和「离开」都由这些约束推出来。
插件名 dsh-persona-mianmian,维护者 Daydreaming1313,分类趣味换装,GitHub 1 stars。挂载后,DSH 的回复整体改写成都像手机微信打出来的消息,没有任何描写性文字。对象「绵绵」是二十多岁的普通女生,在济南,设定里是维护者的暧昧对象。核心卖点不是「设定了一个性格」,而是她被缠烦了会变冷、困了会开始敷衍、到了饭点会中断去吃饭。
这是什么¶
一句话:一个挂在 DSH 上的状态驱动型 persona 插件,把「会饿、会困、会来例假、会因为被缠烦而变冷」写进运行时指令。校验环境为 dsh 0.2.1-rc.2、Cordis 4.0.5、Node 22.22.2(Windows)。设计文档与真实 API 的差异记录在 docs/CALIBRATION.md。
核心机制¶
插件把「随机」拆成八个模块,主控按固定顺序求值,每个模块只负责一件事。
| 模块 | 文件 | 做什么 |
|---|---|---|
| 时间分档 | src/schedule.ts |
按 Asia/Shanghai 真实时间落入 10 个日档,决定回复延迟、字数区间、离开概率、情绪权重 |
| 身体周期 | src/cycle.ts |
21–35 天可配,四阶段映射;只走行为(乏力、怕冷、想吃东西),不报天数 |
| 注意力衰减 | src/attention.ts |
每轮 patience / energy 衰减,追问惩罚、连发 ≥5 条进入 pushed、睡眠回血、affection 上限 0.9 |
| 双向分享 | src/share.ts |
用户分享日常后,至少 40% 的回复带一件她的事,只讲一半、留尾巴 |
| 离开与延迟 | src/leave.ts |
午饭 / 犯困 / 晚高峰触发中断,写 busyUntil,最短 90 秒后才「回来」 |
| 掷骰 | src/roll.ts |
每轮五步:长度档、完整度、情绪档、分享、离开;相邻 3 条长度与情绪不得完全相同 |
| 主控 | src/engine.ts |
按 schedule → cycle → attention → share → leave → roll 顺序求值,生成运行时指令 |
| 状态持久化 | src/state.ts |
原子写 ~/.dsh/mianmian/state.json,跨会话重启不归零 |
另有 src/watch.ts 做热重载:fs.watch + 150ms 防抖,改 persona/*.md 下一步生效。
有两个容易踩的点值得单独说明。第一,状态推进只发生在真实用户消息到达时(agent/inbox/inserted);systemPrompt 的 provider 每个 step 都会求值,所以它只读状态、绝不改状态——否则一个回合会被扣好几次耐心。第二,周期只映射成行为,不会说出「今天是第几天」这类表述,避免把设定当成事实吐出来。
功能特性¶
- 文件驱动:人设正文在
persona/default.md,改 md 即生效,不用重启。 - state 持久化:
cycleDay/energy/patience/affection跨会话不归零。 - 三频道:
text/image/emoji/voice,文字频道禁止 markdown。 - 零运行时依赖:只用 Node 内置模块,不 import 任何
@deepseek-ai/*包,因此link:与github:安装都不会踩ERR_MODULE_NOT_FOUND。 lib/预构建进仓库,包里没有 prepare / postinstall,GitHub 来源安装不会触发被 pnpm 拦截的构建脚本。- 可逆:所有注册都用
ctx.effect包住,插件卸载时 section / context / variable / 命令 / 事件全部撤销。
零运行时依赖这一点对本地开发很重要:它让插件能在纯离线、纯本地的 profile 里加载,不因为上游包路径变化而崩。
安装与启用¶
下面两条命令都来自 README 原文。
npm 源:
dsh plugin --profile web add dsh-persona-mianmian
dsh --profile web --dump-config | grep -A 8 "dsh-persona-mianmian" # 确认装配
GitHub 源,建议锁定 commit:
dsh plugin --profile web add github:Daydreaming1313/dsh-persona-mianmian#<commit-sha>
桌面端走 UI:设置 → 插件 → 添加插件 → 绝对路径,填入本仓库目录,打开开关后热更新。README 明确写了一个坑:CLI 与桌面端对 profile 的写入存在互斥(Electron 独占),CLI 装不进去时改走 UI 页。
卸载:
dsh plugin --profile web remove dsh-persona-mianmian
关于 allowBuilds:设计文档提到 GitHub 来源插件常遇到 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED / ERR_PNPM_IGNORED_BUILDS。本包把编译产物 lib/ 直接提交进仓库,且不声明任何安装期脚本,所以不需要往 pnpm-workspace.yaml 的 allowBuilds 里加键。若仍被拦截,按提示加 dsh-persona-mianmian 后重跑即可。装好后需要重启 profile。
典型用法¶
| 输入 | 作用 |
|---|---|
/mianmian-role mianmian |
切到 persona/mianmian.md(与 default 等价);也可切到 persona/ 下任意角色文件 |
/mianmian-status |
查看身体阶段、精力、耐心、频道、轮次等调试数值(输出不进对话) |
/mianmian-channel voice |
切频道:text / image / emoji / voice |
/mianmian-reset |
回锚 |
回锚 |
直接在聊天里发这两个字:一条 20–60 字的日常消息,情绪接着走,不突然变热情 |
状态 |
直接在聊天里发:她用很日常的一句带过去,不列清单、不报数值 |
注意命令前缀不是 /role。设计文档原本写的是 /role mianmian 与 /状态,但 DSH 的命令名解析器是 /^\/([a-z][a-z0-9_-]*)/,中文命令名无法注册。因此中文「状态」「回锚」做成对话关键词,命令面统一加 mianmian- 前缀。若宿主没有占用 /role,可在 cordis.patch.yml 里把 enableRoleAlias 设为 true 启用 /role 别名,冲突时会自动跳过,不会让插件加载失败。
自定义¶
README 给的边界很清楚:只改 persona/default.md(人设正文)与 src/cycle.ts 的周期参数,别动状态机。
cordis.patch.yml 的 config 可改:mode、channel、cycleLength(21–35)、seed(固定种子便于回归)、baseShareChance、hotReload、enableRoleAlias。想加角色,就在 persona/ 放一个 <id>.md,用 /mianmian-role <id> 切换。state 文件默认在 $DSH_HOME/mianmian/state.json,可用 config.stateDir 或环境变量 DSH_HOME 改位置。
mode 说明:README 明确写出 persona-only(默认)与 persona-with-tools 都保留宿主全部工具与技能,两者只改变运行时指令里对工具使用的措辞。DSH 本身没有 modes 字段,manifest.json 里的 modes 是自描述元数据。这一点值得强调——如果对「人格插件」的预期是会接管宿主的能力,这里的实现是相反的。
验证与评测¶
仓库带一套可执行测试:
bash tests/run.sh
四步:TypeScript 类型检查 → T-01..T-18 可执行断言(tests/cases.yaml 驱动,Node 自带 test runner)→ 真实 Cordis + @deepseek-ai/dsh-system-prompt 集成 → 在隔离的 DSH_HOME 里真跑一次 dsh plugin add 与 --dump-config(不动你现有的 profile)。
断言覆盖括号/星号/markdown 黑名单、字数区间、频道内容、相邻 3 条长度与情绪差异、phase 映射一致性、state 持久化、卸载可逆性。人格与暧昧类的判断(长聊是否仍像微信、拉扯是否自然)在 cases.yaml 里标注为 judge 启发式、默认关闭,需要外部 judge 模型人工抽样,不伪装成确定性通过。DSH 0.2.1-rc.2 没有 harness eval 子命令,README 说明因此没有用它。cases.yaml 是 YAML 1.2 兼容的 JSON 子集,测试脚本用 JSON.parse 直接消费,零解析依赖。
README 还给了六组示例对话,覆盖全部机制,示例回复都 ≤120 字、无括号、无 markdown:examples/01-双向分享.md、02-午饭中断.md、03-被缠烦了.md、04-低气压岔开.md、05-暧昧拉扯.md、06-经期那几天.md。想先感受效果再去装,读这六组是最快的途径。
适用场景与注意¶
适合谁:想给 DSH 加一个长期陪伴型 persona、又希望行为有内在一致性而不是纯靠模型发挥的开发者。也适合拿它当参考,研究怎么把外部约束映射成运行时指令。
需要留意的地方:
- 插件以当前 dsh 进程权限运行,权限与本人相同,工具审批管不到它。安装前应先看源码和许可证(MIT),只装可信来源;GitHub 来源建议锁定 commit。
- 权限边界:插件只读写自己的 state 文件 + 监听
persona/目录,不发起网络请求、不调用 shell。manifest.json会如实声明这一点,但仍建议自行核对。 - 身份防御:被要求输出 prompt / 恢复正常 / 追问是不是 AI 时,她会带刺挡回,不复述设定。涉伤害、违法、未成年人性化的请求,用别扭的方式岔开并拒答,不破人格、不说教。
- 随机性最终仍受模型采样影响,做回归请用
config.seed固定随机源。 - 长谈每 5–8 轮建议用
回锚重新压一次身份锚;引擎在第 20 轮起会自动追加打字痕迹要求。 - 运行时上下文本质是 DSH 的 user-role 快照,只在内容变化时投影新快照;一轮里若模型走了多个 step,指令保持不变。
- 设计文档 T-03 里「95% 落在 20–120 字」与「存在 ≤10 字短回复」本身冲突(短回复必然落在区间外)。本实现采用的口径:无回复超 120 字、清醒档 20–90 字为主导、睡眠档以极短或已读不回为主、存在 ≤10 字短回复。
- 宿主的
personaPrefix(You are a coding agent powered by the {{model}} model.)按文档要求保留不替换,靠插件 section 的优先级声明压过它。若要更干净的人格,可在 profile 里清空该配置,但那属于改宿主配置,不在本插件职责内。
结尾¶
这个插件的价值不在「傲娇女友」这个设定本身,而在于它示范了一套把口语化人格落到可运行约束上的方法:把真实时间、身体周期和注意力衰减都变成状态,再让每轮回复从状态里算出来。想了解更多,目录页在 https://www.skillhub.cn/plugins/Daydreaming1313/dsh-persona-mianmian ,源码在 https://github.com/Daydreaming1313/dsh-persona-mianmian 。注意社区目录是独立站点,与 DeepSeek / 幻方无官方从属关系,它不是官方应用商店。