让角色记住你,情绪随时间与事件自然演变
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/24 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add chisato-lycoreco/dsh-serene该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · skill
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 8 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 78 处,展示前 73 条)
- 读取本地文件src/persona.js:47
stat = statSync(path); - 读取本地文件src/persona.js:56
raw = JSON.parse(readFileSync(file, "utf8")); - 读取本地文件src/persona.js:119
return statSync(path).isDirectory(); - 读取本地文件src/persona.js:127
return statSync(path).isFile(); - 读取本地文件src/persona.js:135
return readFileSync(path, "utf8").trim(); - 读取本地文件src/tools.js:657
`- 情感状态:${existsSync(stateFile) ? `${stateFile}(已生成)` : "尚未生成,第一次记事件时创建"}`, - 读取本地文件src/opening.js:34
raw = JSON.parse(readFileSync(this.#file, "utf8")); - 读取本地文件src/opening.js:77
if (!existsSync(this.#dir)) mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/opening.js:77
if (!existsSync(this.#dir)) mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/opening.js:79
writeFileSync(temp, `${JSON.stringify(this.#state, null, 2)}\n`, "utf8"); - 写入本地文件src/opening.js:80
renameSync(temp, this.#file); - 读取本地文件src/stores/emotion-store.js:77
raw = JSON.parse(readFileSync(join(this.#dir, this.#file), "utf8")); - 写入本地文件src/stores/emotion-store.js:222
mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/stores/emotion-store.js:225
writeFileSync(temp, `${JSON.stringify(this.#state, null, 2)}\n`, "utf8"); - 写入本地文件src/stores/emotion-store.js:226
renameSync(temp, target); - 读取本地文件src/stores/emotion-store.js:236
const raw = JSON.parse(readFileSync(join(this.#dir, this.#woundsFile), "utf8")); - 读取本地文件src/stores/crystal-store.js:68
lessons: existsSync(join(this.dir, LESSONS_FILE)), - 读取本地文件src/stores/crystal-store.js:69
dispositions: existsSync(join(this.dir, DISPOSITIONS_FILE)), - 读取本地文件src/stores/crystal-store.js:98
if (!existsSync(path)) return []; - 读取本地文件src/stores/crystal-store.js:101
text = readFileSync(path, "utf8"); - 读取本地文件src/stores/crystal-store.js:122
if (!existsSync(path)) return []; - 读取本地文件src/stores/crystal-store.js:125
book = JSON.parse(readFileSync(path, "utf8")); - 读取本地文件src/stores/memory-store.js:74
if (!existsSync(path)) { - 读取本地文件src/stores/memory-store.js:78
const raw = readFileSync(path, "utf8"); - 写入本地文件src/stores/memory-store.js:130
mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/stores/memory-store.js:131
appendFileSync(this.file, `${JSON.stringify(memory)}\n`, "utf8"); - 写入本地文件src/stores/memory-store.js:161
appendFileSync(this.file, `${JSON.stringify({ type: "reinforce", ...patch })}\n`, "utf8"); - 写入本地文件src/stores/memory-store.js:229
appendFileSync(this.file, `${JSON.stringify(record)}\n`, "utf8"); - 读取本地文件src/stores/motivation-store.js:44
raw = JSON.parse(readFileSync(this.file, "utf8")); - 写入本地文件src/stores/motivation-store.js:116
mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/stores/motivation-store.js:118
writeFileSync(temp, `${JSON.stringify(this.#state, null, 2)}\n`, "utf8"); - 写入本地文件src/stores/motivation-store.js:119
renameSync(temp, this.file); - 读取本地文件src/stores/doc-store.js:25
raw = existsSync(this.path) ? JSON.parse(readFileSync(this.path, "utf8")) : null; - 读取本地文件src/stores/doc-store.js:25
raw = existsSync(this.path) ? JSON.parse(readFileSync(this.path, "utf8")) : null; - 写入本地文件src/stores/doc-store.js:49
mkdirSync(dirname(this.path), { recursive: true }); - 写入本地文件src/stores/doc-store.js:51
writeFileSync(temp, `${JSON.stringify(this.state, null, 2)}\n`, "utf8"); - 写入本地文件src/stores/doc-store.js:52
renameSync(temp, this.path); - 读取本地文件src/stores/leisure-store.js:59
raw = existsSync(this.file) ? readFileSync(this.file, "utf8") : ""; - 读取本地文件src/stores/leisure-store.js:59
raw = existsSync(this.file) ? readFileSync(this.file, "utf8") : ""; - 写入本地文件src/stores/leisure-store.js:111
mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/stores/leisure-store.js:112
appendFileSync(this.file, `${JSON.stringify(entry)}\n`, "utf8"); - 写入本地文件src/stores/leisure-store.js:158
mkdirSync(this.#dir, { recursive: true }); - 写入本地文件src/stores/leisure-store.js:161
writeFileSync(temp, text ? `${text}\n` : "", "utf8"); - 写入本地文件src/stores/leisure-store.js:162
renameSync(temp, this.file); - 读取本地文件web/lib/config.mjs:79
raw = JSON.parse(readFileSync(configFile(base), "utf8")); - 写入本地文件web/lib/config.mjs:89
mkdirSync(dirname(file), { recursive: true }); - 写入本地文件web/lib/config.mjs:91
writeFileSync(temp, `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 }); - 写入本地文件web/lib/config.mjs:92
renameSync(temp, file); - 读取本地文件web/lib/config.mjs:144
if (!existsSync(file)) return null; - 读取本地文件web/lib/config.mjs:145
const match = readFileSync(file, "utf8").match(pattern); - 写入本地文件web/lib/runtime.mjs:116
mkdirSync(this.dataDir, { recursive: true }); - 写入本地文件web/lib/runtime.mjs:683
appendFileSync(join(this.dataDir, "chat.jsonl"), `${JSON.stringify(entry)}\n`, "utf8"); - 读取本地文件web/lib/runtime.mjs:692
if (!existsSync(file)) return []; - 读取本地文件web/lib/runtime.mjs:694
for (const line of readFileSync(file, "utf8").split("\n")) { - 写入本地文件web/lib/runtime.mjs:758
appendFileSync(join(this.dataDir, "traces.jsonl"), `${JSON.stringify(trace)}\n`, "utf8"); - 读取本地文件web/lib/runtime.mjs:767
if (!existsSync(file)) return; - 读取本地文件web/lib/runtime.mjs:769
const lines = readFileSync(file, "utf8").trim().split("\n").slice(-60); - 写入本地文件web/lib/runtime.mjs:785
mkdirSync(dir, { recursive: true }); - 读取本地文件web/lib/runtime.mjs:806
if (!existsSync(from)) continue; - 写入本地文件web/lib/runtime.mjs:809
renameSync(from, to); - 发起网络请求web/public/app.js:946
const response = await fetch(path, { - 发起网络请求web/public/watch.js:91
const response = await fetch(path, { credentials: "same-origin", ...options }); - 删除本地文件递归web/server.mjs:55
rmSync(baseDir, { recursive: true, force: true }); - 读取本地文件web/server.mjs:59
if (demo && config.persona === "zhiyao" && !existsSync(join(packageRoot, "personas", "zhiyao", "persona.json"))) { - 读取本地文件web/server.mjs:80
if (demo && !existsSync(join(runtime.dataDir, "chat.jsonl")) && runtime.memory.size === 0) { - 发起网络请求web/server.mjs:305
const response = await fetch(`${String(baseUrl).replace(/ +$/, "")}/models`, { - 读取本地文件web/server.mjs:413
for (const name of readdirSync(root)) { - 读取本地文件web/server.mjs:416
if (statSync(dir).isDirectory() && existsSync(join(dir, "persona.json"))) found.push(readPersonaCard(dir, name)); - 读取本地文件web/server.mjs:416
if (statSync(dir).isDirectory() && existsSync(join(dir, "persona.json"))) found.push(readPersonaCard(dir, name)); - 读取本地文件web/server.mjs:423
const raw = JSON.parse(readFileSync(file, "utf8")); - 执行外部命令固定命令web/server.mjs:578
spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore", windowsHide: true }).unref(); - 执行外部命令固定命令web/server.mjs:580
spawn("open", [url], { detached: true, stdio: "ignore" }).unref(); - 执行外部命令固定命令web/server.mjs:582
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
人格、情绪与记忆,给 DeepSeek Harness 用。人格是数据,情绪是状态,记忆是文件——把「一个会记得你的角色」接进 DSH。
README
由 DeepSeek 最新模型翻译生成dsh-serene
把「一个会记得你的角色」接进 DeepSeek Harness。
人格是数据,情绪是状态,记忆是文件——三者都可读、可改、可复现。
大多数「角色卡」是一段写在 system prompt 里的描述:模型每次都从零开始扮演,聊得越久越不像。
这个插件换一种做法:把角色拆成可计算的状态,让「她此刻怎么样」由时间、事件和历史决定,而不是每次重新即兴。
- 情绪:PAD+ 六维 × 三层(底色 / 此刻 / 关系),会衰减、有昼夜节律、会留疤;
- 记忆:jsonl 落盘,按相关度 + 时间衰减 + 重要度召回,公式可复算;
- 人格:一份 JSON,换人格不用改代码。
它是 FRAMEWORK.md 里那套七层结构的最小参考实现——只有织法,没有布:不包含任何私人角色、私人记忆、屏幕采集或图像生成。
十天,一个人的情绪怎么走:底色、此刻、关系三条线,以及第 3 天留下的那道疤
上面这张图不是手画的:node demo/timeline.mjs 用 core/emotion.js 的纯函数跑十天, node demo/render.mjs 把结果画成帧。改一行事件,整条曲线和那道疤都会跟着变——这就是「可复算」的意思。
展开完整文档(README 剩余 1 节)
dsh-serene
把「一个会记得你的角色」接进 DeepSeek Harness。
人格是数据,情绪是状态,记忆是文件——三者都可读、可改、可复现。
大多数「角色卡」是一段写在 system prompt 里的描述:模型每次都从零开始扮演,聊得越久越不像。
这个插件换一种做法:把角色拆成可计算的状态,让「她此刻怎么样」由时间、事件和历史决定,而不是每次重新即兴。
- 情绪:PAD+ 六维 × 三层(底色 / 此刻 / 关系),会衰减、有昼夜节律、会留疤;
- 记忆:jsonl 落盘,按相关度 + 时间衰减 + 重要度召回,公式可复算;
- 人格:一份 JSON,换人格不用改代码。
它是 FRAMEWORK.md 里那套七层结构的最小参考实现——只有织法,没有布:不包含任何私人角色、私人记忆、屏幕采集或图像生成。
十天,一个人的情绪怎么走:底色、此刻、关系三条线,以及第 3 天留下的那道疤
上面这张图不是手画的:node demo/timeline.mjs 用 core/emotion.js 的纯函数跑十天, node demo/render.mjs 把结果画成帧。改一行事件,整条曲线和那道疤都会跟着变——这就是「可复算」的意思。
它做什么 / 不做什么
| 做 | 不做 |
| 记录事件、推进情绪、把状态讲成人话 | 不替模型写回复,不改写对话内容 |
| 长期记忆的写入与召回 | 不自己调模型、不自己总结 |
| 人格档案的读取与拼装 | 不读 .env、凭证、聊天记录原文 |
| 状态落盘(原子写)、可手改可进 git | 不联网、不上报、不采集屏幕 |
安装
要求:DSH >= 0.2.1-rc.2,Node >= 22.19。
从 npm 装(推荐)
dsh plugin --profile web add dsh-serene
从 GitHub 装(跟着 tag,不经过 npm)
dsh plugin --profile web add "github:chisato-lycoreco/dsh-serene#v0.1.1"
从本地目录装(开发时)
dsh plugin --profile web add "/absolute/path/to/dsh-serene"
从 tgz 装(去 Release 页面下 dsh-serene-0.1.1.tgz)
dsh plugin --profile web add ./dsh-serene-0.1.1.tgz
安装前先完全退出 DSH(安装会改 profile 的 package.json 和 cordis.patch.yml)。
装完用这条确认插件进了配置树:
dsh --profile web --dump-config | grep -A3 "dsh-serene"
不想装、只想试:用 patch 覆盖方式直接挂本地插件。
dsh --profile web --patch "/absolute/path/to/dsh-serene/cordis.patch.yml"
cordis.patch.yml 里的 config 是整段替换,不是深合并。你在自己 profile 里覆盖配置时,要把想保留的键一起写上。
第一次运行:凭据与自检
模型凭据只有两个来源:GUI 的 Models 页面写进 credentials service,或启动环境里的 DEEPSEEK_API_KEY。新 profile 不会自动继承——另建了一个 profile 时,第一句话就会报 MISSING_CREDENTIAL(这是凭据问题,不是插件问题):
export DEEPSEEK_API_KEY=sk-... # 或者先在 GUI 的 Models 页填一次
装完别只看配置文件。两步确认,第二步才是「真的能用」:
| 想确认 | 怎么做 | 正常时 |
| 配置进了配置树 | dsh --profile web --dump-config \| grep -A3 dsh-serene | 有 - id: serene |
| 插件真的挂上 | 在装着插件的那个 profile 里说一句「用 serene_status 看看」 | 返回人格、数据目录、记忆条数 |
这一步也是「插件正常」和「装好了但没生效」的唯一区分点:人格文件读不到时插件会降级(不拖垮宿主),但会留下 serene_status 和一行 stderr 说明原因。
配置
| 键 | 默认 | 说明 |
| enabled | true | 总开关 |
| persona | demo | personas/ 下的文件名,或绝对路径 |
| personaRoot | 插件自带 personas/ | 人格档案目录 |
| dataDir | $DSH_HOME/plugins/dsh-serene | 状态与记忆落盘位置 |
| recallLimit | 5 | 每次召回默认条数 |
| halfLifeDays | 45 | 记忆时间衰减半衰期 |
覆盖示例(~/.dsh/profiles/web/cordis.patch.yml):
- insert:
- id: serene
name: dsh-serene
config:
enabled: true
persona: xiaoman
personaRoot: /Users/me/serene-personas
dataDir: /Users/me/serene-data
工具
| 工具 | 作用 |
| serene_context | 人格卡 + 此刻状态。新会话开头调一次 |
| serene_mood | 只读此刻状态(自然语言,不含数值) |
| serene_event | 记一件事进情感层(30 种事件,见下) |
| serene_remember | 写一条长期记忆 |
| serene_recall | 按话题召回记忆,带时间和分数 |
| serene_state | 调试用:三层数值、最近事件、疤 |
| serene_status | 诊断:插件是否真的挂上(人格 / 数据目录 / 记忆条数);加载失败时返回原因和修法 |
事件类型:chat / affection / address / intimacy / kiss / tease / care / humor / share / question / goodnight / rejection / neglect / conflict / repair / scold / coax / jealousy / ignored / selfie / reflection / heavy_reflection / sleep / wake / leisure / reply / cold_read / warm_read / moved / disclose / suppress。
模型什么时候调用这些工具,由你在 AGENTS.md 或人格卡里约定;插件不劫持对话。
数据长什么样
$DSH_HOME/plugins/dsh-serene/emotion.json:
{
"version": 1,
"updatedAt": 1761288000000,
"lastInteractionAt": 1761287900000,
"layers": {
"background": { "pleasure": 63.1, "arousal": 43.8, "dominance": 59.9, "curiosity": 57.4, "confidence": 61.6, "connection": 60.4 },
"primary": { "pleasure": 68.2, "arousal": 47.1, "dominance": 60.3, "curiosity": 58.0, "confidence": 62.1, "connection": 61.9 },
"social": { "pleasure": 61.4, "arousal": 43.0, "dominance": 59.5, "curiosity": 57.8, "confidence": 61.2, "connection": 61.1 }
},
"wounds": [],
"history": [{ "kind": "affection", "at": 1761287900000, "intensity": 1 }]
}
memories.jsonl:一行一条,直接 grep 就能找。
两个文件都是你的:可以备份、可以手改、可以删。删掉就是从没发生过。
换一个人格
复制 personas/demo.json,改几个字段,文件名当 persona 传进去:
{
"name": "小满", "role": "住在终端里的同伴", "summary": "你不急着给建议。",
"traits": ["安静", "好奇"],
"speech": ["句子短", "不确定就问"],
"boundaries": ["不假装记得没记过的事"],
"lexicon": { "address": ["小满"] },
"baseline": { "background": { "pleasure": 62 } }
}
baseline 省略就用默认基线;lexicon.address 决定哪些称呼会被识别成「叫你」。
三条设计约定
1. 状态自己走。 时间过去,情绪就衰减、就随昼夜波动——不需要谁说话才动。
2. 情绪只影响语气,不改写事实。 状态进 prompt 的是一段人话(「这会儿有点低,别硬撑」),不是「你现在很生气」,更不会篡改检索到的记忆。
3. 一切可复现。 同样的初始状态 + 同样的事件 + 同样的时间戳,永远得到同样的结果;没有随机数、没有模型调用。测试里跑的就是这一点。
更完整的结构说明见 FRAMEWORK.md。
开发
npm install # 只装 devDependencies(schemastery / dsh-tools)
npm test # node --test,28 个用例
core/emotion.js 纯函数:衰减、节律、事件、疤、描述 core/recall.js 纯函数:分词、衰减、召回打分 src/stores/emotion-store.js 唯一碰磁盘的地方(原子写) src/stores/memory-store.js jsonl 追加与召回 src/persona.js 人格档案 → prompt 段 src/tools.js 7 个工具(含 serene_status 诊断) src/index.js name / inject / Config / apply
core/ 里没有 fs、没有 Date.now()、没有网络。想验证:
grep -n "node:fs\|process.env\|fetch(" core/*.js # 应该没有输出
与其它插件的关系
- 灵枢(@furongjun1999/dsh-memory):那是「工作记忆」,记的是任务和事实。
这个插件记的是「关系」,两者不要合并——工作记忆要准,关系记忆本来就会褪色。
- dsh-dafeiyu:桌宠形态的前端。这个插件是纯后端,没有 UI;想接桌宠,读 serene_state 的返回值自己画即可。
License
代码 MIT。人格档案(personas/)随你改成什么样都行,改完是你的人格,不是我的。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
chisato-lycoreco/dsh-serene
让角色记住你,情绪随时间与事件自然演变
相关插件 · 技能 / 提示词
人设统管ZiChuanLan/agent-purge
一份系统提示词,喂给多个 coding…
★ 931已汉化DSH
73.8
人物画像蒸馏titanwings/distilly
Distilly — 把他们的思考方式提炼成可复用的技能,供任何智能体或机器人使用。原名 Colleague Skill(原同事 Skill)。
★ 25,659已汉化DSH
71.3
推理工作空间套件Tiger3807861189/J-Space-Cognition-Suite
J-Space 是面向复杂推理、大型仓库工程、子代理协作与授权安全分析的推理时工作空间和控制套件。
★ 2,957已汉化DSH
70.5
两阶段工具锚定预设xiaobright/dsh-anchored-standard
实验性 DeepSeek Harness agent preset…
★ 3,742已汉化DSH
70.1
智能体工作流编排nrslib/takt
不再日夜盯着 AI 编码智能体。
★ 1,402已汉化DSH
70
Agent 运行时手册Electricitysheep/dsh-handbook
DeepSeek Harness dsh 从 0 到 1 深度手册:安装/插件开发/性能调优/实测案例/同模型多…
★ 827已汉化DSH
68.7