Passer au contenu principal
D

dsh-agent-soul

danshuyue/dsh-agent-soul

给 DSH 的 agent 一套会忘事的记忆、一份会变的人格和一份会起伏的心情:人格按小节合并(项目覆盖全局但不吃掉全局),记忆按人的规律衰减(清晰→模糊→只剩印象→遗忘),「心里惦记的事」让她的主动有连续性,心情跟着每轮对话自己判断、自己平复(不注入、无挂饰)。

Installer

dsh plugin --profile web add github:danshuyue/dsh-agent-soul

README

dsh-agent-soul

给 DSH 的 agent 一份会忘事的记忆、一套会变的人格和一份会起伏的心情。

它做五件事:

解决什么
人格用 Markdown 写「你是谁」。项目人格只覆盖同名小节,全局的照常生效 —— 不会因为某个目录里有 AGENT.md,你在全局写的话就整份失效
记忆值得记的事自己进来,随时间变模糊:清晰 → 清楚 → 模糊 → 只剩印象 → 遗忘。忘了不是删掉,是先变得说不清
目的从记忆里长出一份「心里惦记的事」,让她的主动有连续性 —— 不是定时问候,是主动想起
心情每轮对话后自己判一次起伏(心情值 + 七情 + 时间线),不注入提示词。记下来的情绪有两个出口:当日记的底色、推着目的机制的驱动
日记开机时把前一天沉淀成两篇:一篇公开(可以拿给你看),一篇私密(只写给自己)。插件自己调模型生成、自己写文件,不走消息投递

不管 B 站、不管 QQ、不管桌宠。这个插件只关心「她是谁」和「她记得什么」。


日常怎么用

装好之后基本不需要操作。想干预时有三个入口:

1. 什么都不做(大多数时候)

  • 你说话,值得记的事自动进记忆卡
  • 每天第一次打开,它会把不重要的事降一格清晰度
  • 她答应过的事、没弄明白的事,自动变成「心里惦记的事」
  • 开机时自动补写前一天的日记

2. 设置 → 灵魂(七个标签)

标签用来干什么
概览一眼看「到底生效没有」:人格来源、记忆条数与容量、惦记几件事、最近(当前心情 + 昨天日记写没写)
人格直接编辑全局 / 项目人格。存完下一个回合生效
记忆搜、看清晰度和重要度、固定(永不遗忘)、删、手动加
目的四个驱动读数 + 惦记清单,可以「放下了」
心情心情值 + 七情 + 时间线;可重置、可手动跑一次平复
日记开关、兜底检查间隔、公开/私密语气、手动触发(可带备注)、最近日记列表
设置注入预算、叠加还是互斥、各项开关、立即整理一次

3. 直接跟她说话

「记住:我下周一开始做蓝桥杯的题」
「你还记得我之前说过什么关于 STM32 的事吗」
「把那条关于数学建模的记忆忘掉」
「你最近在惦记什么」

工具是 soul_memory(write/recall/forget/pin/list)和 soul_goal(add/done/list)。

文件全是纯文本,也可以直接改:

~/.dsh/AGENT.md                   全局人格
~/.dsh/agents/memory/global.json  记忆卡(改完刷新页面生效)

人格注入

三层来源与优先级

层文件怎么改
1<cwd>/AGENT.md自己的编辑器(跟着仓库走,可提交进 git)
2~/.dsh/agents/projects/<slug>.md设置 → 人格 → 项目人格(本机)
3~/.dsh/AGENT.md设置 → 人格 → 全局人格

项目层只取 1、2 里优先级最高的那一份(不是两份合并)——两份都写着 # 你是谁 时,模型会各听一半,最后谁也不像。

叠加还是互斥

互斥(exclusive)叠加(stack,默认)
有项目人格时全局怎么办整份不注入按小节叠加:项目覆盖同名小节,全局其余保留
你在全局写的通用约定某些工作区完全不遵守,像没读到到处生效,除非项目里显式覆盖同名小节

什么时候注入

会话第一轮注入一次,之后每 15 轮补一次。

不每轮重发(那是纯浪费),也不注入一次就再也不管 —— 上下文压缩之后会把人格正文压掉,那时她会「记得聊过什么,但不记得自己是谁」。15 轮补一次,成本可以忽略,压缩后能自己找回来。

⚠️ 如果 <cwd>/AGENT.md 存在,它会盖住设置页编辑的第 2 层。你在设置页改了、保存了、提示「已保存」,但其实没生效。页面会亮黄框提示。


记忆

什么会被记住

自动提取分三步:过滤 → 提炼 → 打分入库。

1. 过滤(纯规则):只收「人在说话」。丢掉宿主注入的上下文、桥接层的触发模板、信封前缀(「(QQ 私聊·用户本人 [QQ号])」),以及她自己被桥回显的话。

信封前缀有 20 多字,会把「起了起了」这种 4 字短句凑够 8 字门槛,长度过滤直接失效。而回显的那条更糟:她的每句自言自语都会变成记忆,是自我强化的死循环。

2. 提炼(模型):过了过滤的原话不直接入库,先交给模型改写成记忆。

原话:(QQ 私聊·用户本人 [QQ号])今早上7点多才起来。昨晚上在忙着给你做插件,睡的晚了。
记忆:树树今早七点多才起,昨晚熬夜在做插件

提示词里写死三条硬约束:写成「关于树树的一条记忆」而不是复述对话、不许编他没说过的事、没有信息量就输出空数组(宁可少记)。提炼失败一律退回原话直存,不丢信息。

3. 打分入库:过线(42)才写进卡。

你说的结果
「今晚吃牛肉丸子汤」不记 —— 一次性日常,记下来只占位置
「我手上有一块 F103 开发板」记
「我目标是当嵌入式工程师」「我是烟台大学大二」记,且额外写一份进全局卡(换工作区也认得你)

打分是纯规则 + 数值,不调模型 —— 什么时候该记、记多久,确定性比聪明重要。模型只负责把话改写成记忆,不参与「要不要记」。

会忘记

清晰 ──→ 清楚 ──→ 模糊 ──→ 只剩印象 ──→ 遗忘
200字     130字    42字+    只剩标题     移出提示词
                  「记不清了」「好像有…」  进 archive.jsonl

关键不在阶梯,在注入时的措辞:模糊的写成「……(细节记不清了)」,只剩印象的写成「好像有「xx」这件事…(想不起来了)」。模型看到的是「我隐约记得」,而不是「查无此事」或者「细节完备但其实已经不可靠」。

  • 一天只降一档:慢慢模糊,不是某天突然想不起来
  • 重要的事降得慢:普通记忆每天 -0.35 留存,和主人相关的只有 -0.12
  • 永远不会忘的:固定的,和「第一次」类的事
  • 提起来会变清楚:被再次提及就刷新时间、召回 +1,一天回升一档

入库前脱敏(密钥 / 手机号 / 身份证 / 卡号 / 链接 / QQ 号),太短的不记。

注入策略

  • 第一轮:整张卡 + 一段「最近」(见下节)
  • 之后:按当前这句话召回相关几条,同一话题没变就复用上次的文本,不重拼
  • 近似重复:新记忆和已有条目字面相近时,只记一行日志提醒,不自动替换 —— 让字符串算法决定「哪句是真的」太危险

「最近」:跨对话的那条通路

新会话第一轮,除了记忆卡,还会带上:

### 前几天的日记
(最近 2 天的公开日记,超 6000 字留最近的)

### 今天到目前为止(可能来自别的对话)
[14:20] 树树:……
[14:21] 我:……

为什么需要:人不会在一个对话里做完所有事 —— 修插件、QQ 聊天、闲聊是分开的会话。记忆卡只有零碎事实,日记只写到昨天,所以新开一个对话时她不知道今天早些时候在别的对话里做过什么。

数据来源是 conversations/<今天>.jsonl —— 这个流水本来就是所有会话共同汇总的(原来只用来生成日记,从没读回过会话)。

配置 memoryRecentDiaryDays / memoryRecentChatChars 控制,设 0 关闭。私密日记默认不进(memoryRecentDiaryIncludePrivate)。


目的机制:「主动想起」而不是「主动发消息」

四个驱动维度,各 0~1,8 小时落一半:

维度被什么推高
想弄明白疑问句、看不懂的东西
在乎累、难受、熬夜
想分享有意思的发现、推荐
想搞懂学会了、跑通了、踩坑了

再加上从记忆里长出来的「没闭合的事」,渲染成很短的一段(默认 ≤ 320 字,最多 3 条)。里面明确写着「这不是任务清单,不需要每轮都提」。

心情会推着它走:担心/心疼 → 在乎 涨,开心/甜蜜 → 想分享 涨。不是每轮往上加,而是让驱动向情绪推出的目标靠拢(每轮 15%)—— 累加式写法几十轮后会把所有驱动顶到 1,等于没有状态;靠拢则天然有收敛点。

说出去了才算完:渲染出去的目标会被记下来,下一轮看她有没有真的说出口。说到了 → 驱动 ×0.45;超过 6 小时没说到 → ×0.85,慢慢淡了。只判一次,判完移出列表。

本地 DSH 没有推送渠道(你不打开就没有回合),所以「主动」只能是主动想起。真正主动发消息属于 QQ 那边的事。


心情

心情是全局一份(agents/mood/global.json),不按工作区拆。

2026-10-31 改的。原来每个工作区一份,实测四份分数从 71 到 99.95 —— 同一个人换个目录就换一份心情,会出现「刚在 A 里说累、切到 B 又满血」。心情是「这个人现在的状态」,不是「这个文件夹的状态」。

迁移是自动的:global.json 不存在时,从旧文件里挑记录最完整的那份搬过来,旧文件保留不删。

七情的满格是 20 不是 100:判定单次上限 ±10,又被 90 分钟半衰期往回磨,实测一整天峰值只到 13。所以设置页和日记都按 20 当 100% 折算 —— 「踏实 65%」是很踏实,不是刚过一半。

判定用哪个模型:每回合判一次,是全插件最贵的一处固定开销。两条路:

  • 留空 → 跟会话模型(贵,但不用配)
  • moodJudgeProvider: siliconflow + moodJudgeModel: Qwen/Qwen2.5-7B-Instruct → 走硅基流动的小模型

实测(同一批 6 个样本,方向判断 6/6 一致):输入 token 2015 → 1182,延迟 1.52s,成本降到零头。提示词也一起压过(2875 → 1646 字),压的时候踩过一个坑:把幅度写成「±14」会丢掉正负号,模型会把「没吃饭」判成好事。

密钥怎么存:设置页「判断模型」下面有一个密码框,粘进去点保存即可。Windows 上用 DPAPI(CurrentUser)加密 —— 密文绑你的账户,拷到别的机器解不开;文件在 ~/.dsh/agents/secrets/siliconflow.bin,里面没有明文。非 Windows 平台会退化成 base64 + 0600 权限,并在日志里明说「这不是真加密」。

不要把它写进 persona.json。那个文件是明文,会被同步、会被整份贴进 issue。

心情会影响语气(2026-11-01 新增)

心情不只是个记在后台的数字 —— 它会偶尔在对话里露一句。规则很克制:

  • 三档:心情偏离基准超过阈值才算「低」或「高」,中档什么都不加(大多数时候都是中档)
  • 不是每轮都加:低/高也要隔 N 轮才挂一次,默认 7 轮,设置页可调
  • 挂在消息上,不进系统提示:心情是「那一刻」的状态,该像照片上的日期一样跟着那条消息冻结。放在系统提示里会随心情每轮变化,长缓存全失效,而且永远在过时

挂上去长这样:

[心情:今天不太有劲,说话会短一些,不太想解释。(这只是当时的状态,不是这个人变了。)]

最后那句隔离说明是必须的:它划清「今天的力气」和「这个人是什么样」——人设本身一个字节都不动。

设置页在「心情 → 判断模型」下面:开关、每几轮、偏离多少算低/高。

实现上的一个诚实说明:DSH 没有「改写当前用户消息」的钩子,所以这条心情是另外投递的一条 user 消息(source.kind = 'soul-tone'),不是真的把原消息改写了。效果上是那句话后面跟了一条心情。

它也不会污染记忆:soul-tone 在 INJECTED_SOURCES 里,记忆提取会跳过。


日记

规则:开机时写前一天。

当天写只写得到半天;第二天再写,昨天的窗口已经完整闭合。而且「开机写」和「一直不关机」用的是同一条逻辑 —— 都是补昨天,不用区分机器什么时候开。

DSH 启动 5 秒后 → 看昨天的两篇在不在
  ├─ 都在 → 跳过(同一天重启多次不会重写、不会白花钱)
  └─ 缺了 → 生成并写盘 → 再往前找有没有更早漏的,补一天

看门狗每 30 分钟 → 同上检查(兜住「一直不关机」的情况)

怎么写的:插件自己调 llm.stream 生成,再用 Node 的 fs 直接写文件。

2026-10-31 改的。原来是把一条消息投进会话,让 agent 自己用工具写。可 followup() 是同步返回 void 的,旧判据「没抛异常就算成功」对 void 函数恒为真 —— 日志天天报「已发送」,会话里却零命中(解了 4 个会话、32 个 inbox 事件,schedule 来源的 0 个)。

现在两道就够:生成 → 写盘。写完就在那儿,没有中间态可以静默失败。

生成时不先做摘要:直接把对话原文喂进去(超上限才截尾部)。实测摘要会把 17945 字压成 128 字,且挑的全是窗口开头那段,同一天后半段整个被丢掉。

两篇的隔离靠调用顺序:先写公开(只带公开历史),落盘成功后才写私密(这时才给全部历史)。写在一条里的话,私密历史就摆在下面,她写公开那一步已经看到了本不该给人看的内容。

「私密」完全靠提示词措辞 —— 文件是明文,没有加密。设置页的「最近日记」只展示公开那篇,私密只显示有/没有。

日记里带着当天的情绪

除了对话原文,还会附一段情绪底色:心情从多少走到多少、七情峰值、几段「当时她自己的想法」。

  • 公开给 3 条,措辞是「可以写进去,但这是会拿给对方看的版本」
  • 私密给 6 条,措辞是「这是你当时的真实想法,不用收着」

只给事实与素材,不给「你要写得开心一点」这类指令 —— 一旦让她照着情绪演,日记就假了。

日记之间的连续线(2026-11-01 新增)

昨天写了「明天要把那个 bug 收掉」,今天得有个回声 —— 否则四篇日记就是四篇好文章,不是一个人的四天。

一条「线」就是一件悬着的事,存在 ~/.dsh/agents/diary/threads.json。

开线:写完某天的私密篇后,从正文里按句式挑(明天/下周/还得/没弄完/还没/回头),一天最多 2 条。

判据是「句首不是『他说/树树说』」,不是「整句必须含我」。实测「那个压缩阈值的坑明天还得再收一遍」整句一个「我」都没有 —— 中文本来就会省略主语,用「必须含我」会漏掉最该抓的那条。

关线:今天日记里出现了这条线的任意 6 个字窗口,就算提到、关掉。

不用「首尾都要出现」:线里自带「还没/明天」,收尾时一定换成「收掉了/弄完了」,首尾永远撞不上。

过期:挂满 7 天没人提就自己关。人会慢慢放下,不必假装永远记着。

喂给模型时的措辞是这里最要命的东西 —— 写成「待办」就立刻变周报:

还悬着的事(这是惦记,不是任务清单) 写的时候:今天提到了、有进展,就自然带一句;没提到就完全不用管。 别为了「交代进度」硬塞 —— 日记不是周报。

threads.json 坏了就当没有,日记照写 —— 连续线是锦上添花,不该让日记整个失败。

一天的边界

自然日。 日记 2026-10-31 覆盖的是 2026-10-31 00:00 ~ 2026-11-01 00:00 的对话。心情时间线按同一个窗口筛,两边才对得上。

2026-10-31 从「22:00 分界」改成自然日。原来那套是给「当晚十点写日记」设计的 —— 晚上十点时「今天」在感受上还没结束。但现在已经改成「第二天写前一天」,那个理由不成立了:隔一天再写,昨天一整天早已闭合,写的是哪一天就该是哪一天。

对话是怎么拿到的

不是写日记时才去翻会话(冷会话拿不到),而是平时就攒:

每轮对话(tick 注入时)
  └─ 扫描新增的 user/message + assistant/message
  └─ 追加到 ~/.dsh/agents/diary/conversations/YYYY-MM-DD.jsonl

过滤口径跟记忆提取一致。助手那边只记她说出口的话:reasoning 和工具调用都不进。实测一个活跃会话里 90% 的助手消息是思考过程、只有 10% 是真正在说话,全收的话一天能攒几十万字。

JSONL 默认保留 7 天,写日记时顺手清理旧的。

手动触发

设置页「日记」页签点「现在写日记」,可以选日期、加备注。或者:

curl -X POST http://127.0.0.1:<PORT>/api/soul/diary \
  -H 'content-type: application/json' \
  -d '{"date":"2026-10-31","note":"今天发生了一些特别的事"}'

安装

# 1) 取代码
git clone https://github.com/danshuyue/dsh-agent-soul.git "<DSH_HOME>/plugins/dsh-agent-soul"

# 2) 挂进 profile:<DSH_HOME>/profiles/desktop/package.json
#      dependencies 加  "dsh-agent-soul": "link:../../plugins/dsh-agent-soul"
#      dsh.profile.bundles 加  "dsh-agent-soul"
# 3) 建 junction(Windows)
mklink /J "<DSH_HOME>\profiles\desktop\node_modules\dsh-agent-soul" "<DSH_HOME>\plugins\dsh-agent-soul"

# 4) 重启 DSH → 设置 → 灵魂

⚠️ 改完 lib/ 要同步到运行副本(profiles/<profile>/node_modules/<插件>/)—— DSH 加载的是那份,不是 plugins/ 里的源码。同步完拿两边哈希对一下,别靠「我改过了」这个印象。


花钱的地方

项频率大约
人格首轮 + 每 15 轮你写多少就是多少
记忆每回合默认 3200 字预算,实际约 2200~3000 token
目的每回合≤ 320 字,约 300~500 token
日记每天一次一次生成调两次模型(公开 + 私密)
心情判定每回合压缩后约 800 token;配小模型后单价降一个数量级

DSH 按 4 字符/token 粗算,中文真实是它的 1.4~1.9 倍。

觉得贵:先砍 injectMaxChars,再考虑关「目的机制」,最后再考虑关心情判定(关了「小波动」就没了)。


配置

<DSH_HOME>/agents/persona.json:

键默认说明
modestackstack 叠加 / exclusive 互斥
includeGlobaltrue全局人格是否参与注入
projectPersonaEnabledtrue是否启用项目人格
injectMaxChars3200记忆注入预算
memoryRecalltrue注入时按当前消息召回相关区(关掉 = 每轮回退整卡)
memoryRecallTopK3相关区最多召回几条
memoryRecentDiaryDays2第一轮带几天日记(0 = 关)
memoryRecentChatChars3000第一轮带多少字今天流水(0 = 关)
memoryRecentDiaryIncludePrivatefalse「最近」里是否含私密日记
memoryRefinetrue自动提取时先让模型提炼(关掉 = 原话直存,会变流水账)
memoryRefineProvider / memoryRefineModel""提炼用的模型(空 = 跟随会话模型)
memoryEnabledtrue是否注入记忆(关掉仍在后台记)
projectMemoryEnabledtrue项目记忆独立一张卡
purposeEnabledtrue是否注入「心里惦记的事」
diaryEnabledtrue是否启用日记
diaryWatchdogMinutes30兜底检查间隔(分钟)
diaryBackfillDays4写新日记时参考前几天的旧日记
diaryOverwritetrue同日重复触发是否覆盖
diaryManualNotetrue手动触发是否允许附备注
diaryPublicTone / diaryPrivateTone""语气(空 = 让她自己把握)
diaryMaxConversationChars30000喂给日记的对话上限(超了留尾部)
diaryPublicMoodMoments3公开日记带几条「当时的想法」(0 = 不带)
diaryPrivateMoodMoments6私密日记带几条
diarySummarizeProvider / diarySummarizeModel""生成用的模型(空 = 跟随会话模型)
diarySummarizeTimeoutMs30000生成调用超时
diaryJsonlKeepDays7对话 JSONL 保留天数

记忆引擎参数(衰减天数、阈值、压缩)在 lib/memory.js 的 DEFAULTS,全部有注释说明为什么是这个值。

diarySchedule(默认 "22:00")已完全停用:不再触发定时(定时器已删除),也不再参与日记的「一天边界」(现在是自然日)。保留这个键只是为了不让旧配置报错。


数据在哪

<DSH_HOME>/AGENT.md                              全局人格
<DSH_HOME>/agents/projects/<slug>.md             项目人格
<DSH_HOME>/agents/persona.json                   配置
<DSH_HOME>/agents/memory/global.json             全局记忆卡
<DSH_HOME>/agents/memory/projects/<slug>.json    项目记忆卡
<DSH_HOME>/agents/memory/purpose.json            目的状态
<DSH_HOME>/agents/memory/archive.jsonl           遗忘档案(append-only)
<DSH_HOME>/agents/mood/global.json               心情状态(全局一份)
<DSH_HOME>/agents/diary/conversations/YYYY-MM-DD.jsonl  当天对话流水(所有会话汇总)
<DSH_HOME>/agents/diary/public/YYYY-MM-DD.md     公开日记
<DSH_HOME>/agents/diary/private/YYYY-MM-DD.md    私密日记(明文,靠提示词区分)
<DSH_HOME>/agents/diary/threads.json              日记连续线(还悬着的事)
<DSH_HOME>/agents/secrets/<name>.bin              密钥(Windows 上是 DPAPI 密文,无明文)
<DSH_HOME>/agents/host-probe.json                 宿主自检失败时的现场(正常时为空/不存在)
<DSH_HOME>/agents/soul.log                       日志(超 2MB 归档成 soul.log.<时间戳>,留 5 份)

记忆卡旁边可能还有两种文件:global.json.corrupt-<时间戳>(读坏时的原文备份,数据没丢)和 global.json.bak(上一代)。

全是纯文件,没有数据库。备份就是复制目录。


排障

grep "\[apply\]"  ~/.dsh/agents/soul.log | tail -3   # 三段注入有没有起来
grep "\[extract\]" ~/.dsh/agents/soul.log | tail -5  # 自动记忆记了几条
grep "\[memory\]" ~/.dsh/agents/soul.log | tail -5   # 衰减/压缩/丢弃了什么
grep "\[diary\]"  ~/.dsh/agents/soul.log | tail -10  # 写没写、跳过原因
grep "\[mood\]"   ~/.dsh/agents/soul.log | tail -5   # 心情合并/读取/语气注入
grep "\[host\]"   ~/.dsh/agents/soul.log | tail -5   # 宿主接口形状自检(有 ⚠️ 就是变了)
grep "语气注入"    ~/.dsh/agents/soul.log | tail -5   # 心情有没有真的挂到消息上

心情语气一条都没挂出来:① 看 moodToneInject 有没有被关 ② 看当前是不是中档(中档故意什么都不加)③ moodToneEveryTurns 是几 —— 默认 7,前 7 轮本来就不挂 ④ 阈值设成 20 的话要心情掉到 50 以下才会分档。

设置页「灵魂」不出现:① 插件是不是真加载了(看 [apply] 启动)② 目录名 / package.json 的 name / cordis.patch.yml 的 name 三处是否完全一致 ③ 重启 DSH

改了 lib/ 没反应:多半是没同步到运行副本,见上面安装那节的 ⚠️。

人格变了但没生效:DSH 只在渲染文本变化时才重新追加,改完在下一个回合生效。

记忆一直在掉条数:archive.jsonl 里能查回来。不该忘的就在设置页固定(固定后永不淘汰)。

日记没写:

  1. grep "\[diary\]" 看是「跳过」还是「失败」—— 跳过说明两篇都在或功能关了
  2. 「写未完成」= 生成失败,日志上一行会写原因(模型取不到 / 超时 / 写盘失败)
  3. 同一天重启多次只有第一次会写,之后是「跳过」,这是设计

日记内容不像自己:diaryPublicTone / diaryPrivateTone 留空时语气全靠模型发挥;写两句具体的(「温暖但克制」「真实、不设防」)比写「好好写」有用得多。

她突然什么都不记得了(最阴的一种):插件读的全是 DSH 的内部字段(事件正文在 data.content、session.snapshotEvents、header.origin)。宿主一升级这些就可能变,而变了不会报错——扫描照跑、候选恒 0、日志一句不说。

所以每个会话第一轮会做一次形状自检:grep "\[host\]" 有 ⚠️ 就是没通过,同时概览页会亮红,现场留在 ~/.dsh/agents/host-probe.json。自检的判据是「能不能真的抠出正文」,不是「函数在不在」——上一回事故里函数全都在,只是返回了空串。真发生了就去改 lib/host.js 的 firstTextOf 和 lib/extract.js 的 messageContentOf。

记忆卡读坏了:原文件会被改名成 global.json.corrupt-<时间戳> 留在原地(数据不会蒸发),概览页亮红;只有当改名都失败时才会进入只读模式,这时新的记忆暂时写不进去,但旧文件不会被空卡盖掉。处理完把 .corrupt- 那份捡回来人工合并即可。


开发

npm test              # 全量:先构建 client,再跑全部单测
npm run build:client  # 只把 lib/client/*.js 拼成 lib/client.js
npm run test:memory   # 只跑记忆引擎
npm run test:diary    # 只跑日记
npm run test:smoke    # 用假宿主跑一遍 apply():注入段 + 工具 + API
npm run test:client   # 先构建,再用假 __ModuleLoader__ 跑一遍浏览器 bundle

单测只碰纯函数,用临时目录,不动真实的 ~/.dsh。

浏览器半侧是拆开写的,lib/client.js 是生成物

DSH 客户端模块系统一个包只出一个 bundle、兄弟文件无法同步 require,所以浏览器半侧不能真的拆成互相 import 的文件。源码按职责放在 lib/client/:

styles.js  api.js  components.js                      基础
overview-tab.js  persona-tab.js  memory-tab.js        标签页
purpose-tab.js  mood-tab.js  diary-tab.js  settings-tab.js
app.js                                                 外壳

tools/build-client.mjs 按固定顺序把它们拼回唯一入口 lib/client.js。改浏览器半侧就改 lib/client/ 里的片段,然后跑 npm run build:client(npm test 会自动先构建)。

两个测试是装进 DSH 之前唯一的保险,因为这两类错误 node --check 抓不到:

  • smoke:apply() 在某个 ctx 服务上炸掉 → 插件加载了但注入没生效
  • client:浏览器 bundle 里用到宿主没提供的全局 → DSH 报 1 entry did not activate 直接起不来

两个都真实发生过,所以留下了。


许可

MIT

Plugins associés