@nightpainters/dsh-ling 1.5.3 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,121 @@
2
2
 
3
3
  本项目遵循语义化版本:`MAJOR.MINOR.PATCH`。
4
4
 
5
+ ## 1.6.1
6
+
7
+ **3×3 红蓝对抗审计的修复版 · 0 条阻断发布 · 14 条该修全部修完**
8
+
9
+ > 一句话:1.6.0 发布前跑了 **红队 3 路 + 蓝队 3 路**的对抗审计(6 个独立审计员),总账 **0 条阻断发布 / 14 条该修 / 10 条观察**;本版把 14 条该修**全部修完**,并修掉 1.6.0 提交后 CI 暴露的一处脆弱断言。
10
+
11
+ > ✅ `package.json` 版本号为 **`1.6.1`**。**无新功能、无 schema 变化**(仍是 v17);⚠️ 修复多落在 **host 侧** ⇒ **需重启宿主**才生效。
12
+
13
+ ### 一、注入面(记忆开场)
14
+
15
+ - **置顶条目现在真的"必进"**:此前 `maxItems` 与字预算**先于**置顶判定生效 ⇒ 置顶数超出上限时,多出来的置顶会被挤掉(穷举 90 组实测**累计丢 117 条**;真机 9 条置顶 + 上限 3 ⇒ 只进 3 条)。修法:两道闸门对 `pinned` **豁免**。复测:9 条置顶 + 上限 3 ⇒ **进 9 条**,穷举 90 组**丢 0**。
16
+ - **落选者留痕的 `source` 恒为 `undefined`**:深条目路径读的是 `row.source`,而该列列名是 `src` ⇒ 那一栏一直是空的。同批把 `snapshot.js` 的字段白名单补上 `src/kind/durability/hit/pinned`(否则面板拿不到这几列)。
17
+ - **打分理由与公式不符(两条路)**:`why` 串按"加法"报分项,而真实公式是"分项先加、再乘血缘与矛盾系数" ⇒ 改成 `(分项和) × 血缘 × 矛盾 ⇒ 总分`,**概述路与条目路**同时改(第一遍只改了条目路,被断言当场抓住)。
18
+ - **概述路的"热度"漏乘类别权重**:同一分数在留痕里被报成两种值(daily 差 0.252、feeling 高出 88%)⇒ 改报**类别加权后**的值。
19
+
20
+ ### 二、库与检索
21
+
22
+ - **`recall` 检索档印的"下一步"用错坐标系**:条目里的 `seq_from` 是**事件流 seq**,而 `recall(conv, from, to)` 收的是**对话轮序号**。实测 274 条可比条目里 **207 条(75.5%)**照那条指引会 out-of-range,偶尔落在范围内的也只能读到不相干的轮次。修法:**不再印 `from`**,改为"先 `recall(conv=…)` 拿对话目录,再按目录里的 `#N` 取原文"。
23
+ - **回灌会把网页端会话升格成"主人自己的 DSH 会话"**:`restoreEntry` 原先按 id 形状回推来源(裸 uuid ⇒ `dsh`),而 **1,523 条网页端会话全是裸 uuid** ⇒ 一次回灌就让该会话多出 `source='dsh'` 的原文行,概述器当场认领 ⇒ 同 id 双概述、双份记忆。修法:id 带 `import:` 前缀仍记 `import`,其余用**调用方声明的**来源;并在工具层加**交叉校验**(声明与归档记录不一致 ⇒ `source-mismatch` 拒)。
24
+ - **飞书通道里主人自己的原话被当成"注入"**:真人判据原先要求 `rpcId`,而**飞书桥来的原话没有 rpcId**(全盘 436 条无 `rpcId` 的 user 消息里 **55 条是主人真话**)⇒ 这些句子在 `recall` 读档时被折进"(此处折叠 N 条注入消息…)"、目录里印成「(注入)」,**"你说过什么"整段消失**。修法:**会话级分通道**(取 `session` 事件过 `isMemoryEligibleHeader`)。实测:真人轮 **951 → 1001**(+49 飞书真话 +1 探针),**子代理委托种子 387 条零回归**。
25
+ - **锁竞争下插件加载失败**:第一抛出点是 `PRAGMA journal_mode=WAL`(而 `busy_timeout` 排在它**之后**一行 ⇒ 撞锁 **23 毫秒**就抛、连等都没等)。修法一:`busy_timeout` 挪到所有可能撞锁的语句**之前**;修法二:构造失败对**锁类错误**(`errcode 5/6`)做 **3 次 × 2 秒有界重试**,仍失败则打印可执行的排查指引后**降级加载**(不注册记忆工具,不影响其它插件)。
26
+
27
+ ### 三、工具与写入边界
28
+
29
+ - **「只有概述、没有原文」的记忆永远忘不掉**:遗忘前的归档守卫要求"原文轮数 > 0",而这类条目(主脉记忆 / 导入条目,真库实测 3 条)的内容**就在概述里** ⇒ 判据改宽;同批修正 `archive.js` 的"空归档"判据 —— 它原来把**只有元数据空壳**的会话也算成"有内容"(⇒ 真空归档会被放行,而遗忘标记一旦落下就永不召回)。
30
+ - **枝名在回执 / 留痕 / 库里三处不一致**(请求 70 字 ⇒ 库里 60 字):统一常量 `BRANCH_NAME_MAX = 80`,并把改名改成**写后读回** —— 回执与留痕一律报库里的真值。
31
+ - **`unlink` 的假回执**:连边可被反向重复写入(`link A→B` 后再 `link B→A` 落两行),而删除只删一条 ⇒ 回执说"断开了"、树里还连着。修法:写入侧查反向(但**有向边不合并** —— `prerequisite`/`supersede` 的反向是不同语义,渲染以 `→` / `↔` 区分),删除侧双向删。
32
+ - **`weightScale` 传 `null` 会静默写成 0**:`Number(null) === 0`,而 0 在界面上是**合法读数** ⇒ 改成先判类型(`null`/`true`/`[]` 一律拒)。
33
+ - **"被采用"的信号按请求窗口加固**:此前按"请求的区间"给条目计数 ⇒ **没被交付的轮次也 +1**;改用**真正交付**的区间。
34
+ - **范围限制改为"如实标注"**:宿主没提供会话清单时,检索档开头写明「⚠️ **没能限定范围** —— 下面这份结果**可能含子代理或其它会话**」,不再静默 fail-open。
35
+
36
+ ### 四、诊断面
37
+
38
+ - 补 `dbg.diff_stats`(差分路的丢弃画像:扫过多少 / 写了多少 / 丢了哪些类型)。此前它只在"一条都没写进去"时留一次记录 ⇒ **正常路径上丢了多少完全不可见**(1.6.0 的原文写入排查就卡在这一条上)。
39
+ - 同批:`dbg.diff_stats` 与 `dbg.event_stats` 现在**每次重启都清空**(此前前者会跨重启残留,看起来像"刚跑过"),`evStats` 计数与 kv 的归零点对齐。
40
+
41
+ ### 五、CI
42
+
43
+ - `tests/bodylimit.test.mjs` 的一条断言写的是墙钟下界 `dt >= 20`(毫秒),CI 实测 **19 ms** ⇒ 红。这条断言在断言**定时器的实现细节**(毫秒级钟有粗粒度、定时器可提前一两毫秒),不是行为缺陷 ⇒ 下界改为 `>= 1`(只证明"确实等过、不是同步 reject"),上界 300 ms 与"结局来自那道闸"由同组另两条钉住。
44
+
45
+ ### 审计出处
46
+
47
+ - 红队三份:`plans/AUDIT-1.6-红队1-注入面.md` · `AUDIT-1.6-红队2-库与检索.md` · `AUDIT-1.6-红队3-工具与边界.md`
48
+ - 蓝队三份复核(逐条判真伪与严重度,并复核"修复是否成立"):`plans/AUDIT-1.6-蓝队1-注入面复核.md` · `AUDIT-1.6-蓝队2-库与检索复核.md` · `AUDIT-1.6-蓝队3-工具与边界复核.md`
49
+ - 总账:**0 条阻断发布 · 14 条该修(全部已修) · 10 条观察**(观察项不阻塞发布,已记账)。
50
+
51
+ ## 1.6.0
52
+
53
+ **找回原文(能读回一年半前的原话)· 深层库(结论层)· 记忆开场换料**
54
+
55
+ > 一句话:本版三件事 —— ① **器灵第一次能读回原文**:此前它手上 7 个工具**没有一个能读会话原文**,开场里那几行概述是"别人替它挑的";现在新增第 8 个工具 `recall`,四档下钻直通磁盘真全文,并把**一年半的网页端历史(1,523 会话 / 726.7 万字)补进了库**;② **深层库**:在"会话流水"之上长出**结论层** —— 从 1,623 个会话里提炼出 **393 条**一句话结论(承诺/决定/事实/偏好/关系),每条都能点回原话;③ **记忆开场换料并瘦身**:开场不再注入"会话概述",改注入**深层库条目**,占用从 1,085 字降到 **约 460 字**。
56
+
57
+ > ✅ **本节即 1.6.0 的发布内容**;`package.json` 版本号为 **`1.6.0`**。
58
+ > ⚠️ 本版多处改动落在 **host 侧**(工具注册、注入面、库 schema)⇒ **需重启宿主**才生效;库 schema 由 **v14 → v17**(全部为 `CREATE TABLE IF NOT EXISTS` / `ALTER TABLE ADD COLUMN`,启动时自动迁移)。
59
+
60
+ ### 一、找回原文:`recall`
61
+
62
+ - **此前为什么读不到**:器灵侧 7 个工具(`tree_read` / `branch_edit` / `memory_forget` / `memory_backfill` / `rule_add` / `habit_propose` / `habit_resolve`)**无一能读原文** —— 它对过去的全部认识止于开场那 8 行概述。README 旧版只列了 2 个工具,同批更正为 8 个。
63
+ - **四档下钻(一次一层,不替你挑内容)**:
64
+ 1. `recall()` ⇒ **会话清单**(磁盘上有原文的会话,最近活动在前);
65
+ 2. `recall({ q })` ⇒ **按内容找会话**(宿主跨会话全文检索;默认只搜**主人自己的会话**,被范围挡掉的条数**一律报出**,越界需显式 `all`);
66
+ 3. `recall({ conv })` ⇒ 该会话的**对话目录**(每轮一行:序号/时间/角色/前 70 字),**不给正文**;
67
+ 4. `recall({ conv, from, to })` ⇒ 那几轮的**原文全文**。
68
+ - **它不替你挑"最重要的 1 万字"**:单次正文上限(`RECALL_READ_CHARS = 12000`)只是**护栏**,且**溢出必报**"还有 N 轮没给,下一次从 N+1 读" —— 工具只给坐标系,**选哪一段是器灵自己的判断**。
69
+ - **真人判据**(五条,2026-10-04 晚修正):① `source.kind === 'user'`;② **会话级**分流 —— 会话头过 `isMemoryEligibleHeader`(与捕获侧同一把尺,子代理会话整段不算);③ **合格会话里没有 `rpcId`** 的 user 消息**也算主人**(飞书桥来的原话就没有 rpcId);④ 正文行首 `[身份·` 仍算注入;⑤ **取不到会话头时只认 `rpcId`**(与修正前逐字同解,保住无头夹具与旧日志)。⚠️ 为什么必须落在**会话级**:事件里**没有通道字段** —— 飞书真人、身份注入块、子代理委托种子在**消息级完全同形**。实测:真人轮 951 → **1001**(+49 飞书真人 +1 探针),子代理种子 387 条**零回归**。
70
+ - **读不到时回落查库**:磁盘没有该会话日志时改读 `dsh_turns_raw`,并在回执里**明说"库那份是残的"**(只含真人消息与有正文的回复)—— 材质差异必须让读的人知道。
71
+ - **会话文件三代代次**:匹配 `session(\.v\d+)?\.jsonl\.zstd`(v4 / v3 / 无版本)取最高一代。此前**写死 `session.v4.jsonl.zstd`** ⇒ 旧版会话"能列出、读不到",同批修复(可读会话数 335 → 465)。
72
+ - **性能**:会话日志是**多 frame zstd**,`zstdDecompressSync` 只解第一帧、流式解压报 `Unknown frame descriptor`;靠 magic 逐字节暴力试解是 **O(n²)(实测 119 秒)**。正解是**纯结构扫描帧边界**(照 DSH 官方 `scanZstdFrames` 重写、**不 import 官方包**),实测 **516 帧扫描 1 ms + 全量解压解析 53 ms**。
73
+
74
+ ### 二、网页端历史补录(一年半)
75
+
76
+ - `deepseek_data-2026-09-06/conversations.json`(24.15 MB / **1,523 条**)⇒ 写入 `dsh_turns_raw`:**1,523 会话 / 4,821 行 / 7,266,628 字 / 耗时 186 ms**;时间跨度 **2025-01-31 → 2026-09-06**。
77
+ - 只收 `REQUEST` / `RESPONSE` / `THINK` 的正文;`SEARCH` / `FILE` / `TOOL_*` 共 5,854 个 fragment **实测全为 0 字符**(纯元数据),丢弃无损;思维链(`THINK`,236.5 万字)本轮**有意不收**(源文件与幂等脚本均在位,可随时重跑)。
78
+ - `source='dsweb'`、会话键用**裸 uuid** ⇒ 概述器与习惯生成器**都不会认领它们**(判据是 source 优先)。
79
+ - 同批把 `dsweb` 加进 `KNOWN_FOREIGN_RAW_SOURCES` —— 它是**无前缀来源**,不列白名单会被判成"未知来源"并在库里挂假警报。
80
+
81
+ ### 三、深层库:结论层(`deep_item`,schema v16)
82
+
83
+ - **它是什么**:浅层库是"会话流水"(含工具调用与调试噪声),深层库是**结论** —— 一条一句话、能独立读懂、带 `conv_id + seq_from/seq_to` 可点回原话。主人给的架构口径逐字:「从浅层记忆中提取关键的东西落入深层库,把工具调用、过时历史剥离掉……然后在召回的时候再重新装配」。
84
+ - **五类** `kind` = 承诺 / 决定 / 事实 / 偏好 / 关系;**三条硬判据**:与主人有关 · 可跨会话复用 · 不是过程与当场读数。**空结果是正确结果**(大量通用问答按判据判 0,未凑数)。
85
+ - **全量提炼**:1,623 个会话 / 208.9 万字给料 / 118 批 ⇒ **393 条**;密度 **`dsh` 6.0 条/会话 vs `dsweb` 2.3 条/会话**(工作会话有结论、网页端多为通用问答)。
86
+ - **三条写进列注释的语义位**(谁改都不许违反):① `hit_count` **只由"真被采用"推进,不由"被注入"推进**;② `superseded_by` **只追不替**(旧的留痕、不删);③ `branch_id` 是**气根**(枝侧反向引用,不动树结构)。
87
+ - **"被采用"已接线**:`recall(conv, from, to)` 取到与条目区间**相交**的原文 ⇒ 该条目 `hit_count +1` 并在回执里报出。挡的是"注入越多 → 分越高 → 越容易被注入"的**自我强化回路**。⚠️ 区间必须用 `dsh_turns_raw.seq`,**不是**目录里的对话轮序号。
88
+ - **条目硬度 `durability`(schema v17)**:`durable` **121** · `long` **159** · `ephemeral` **113**(**重判后**的分布 —— 首轮标注偏松、把"开发期技术结论"记成了长期,蓝队复核时 146 条改档) —— **从根源判断条目自身的临时/永久性**,而不是拿"同一次会话最多几条"这类机械限额去卡;**`ephemeral` 不进开场**。
89
+
90
+ ### 四、记忆开场:换料 + 瘦身
91
+
92
+ - **数据源**:`conv_overview`(会话概述)⇒ **`deep_item`(结论条目)**。两条路**并存**:库里还没有条目时回落旧路(否则尚未提炼的会话会在开场里整个消失)。
93
+ - **判据**(面向深层库条目,不再面向概述,六项相加后乘两个系数):**置顶必进** + **被反复采用**(5 次饱和) + **当轮关键词**(权重 1.2 —— 要能把"他这一轮问的那件事"顶进前几席,所以它高于长期分) + **硬度分**(`durable` +0.6) + **类别分**(承诺 .4 / 关系 .3 / 偏好 .2 / 决定 .1 / 事实 0 —— 让"关于人"的排在"关于事"的前面) + **新近**(半衰期一年、权重 **0.1**) × **血缘档位** × **矛盾降权**。血缘与矛盾两张表按 `source|conv_id` 索引 ⇒ 换料后**一个字没改**。
94
+ - **行形态**由 `- [标签] (日期) 标题 —— 摘要… (来源: …)` 变成极简的 **`- [承诺] 一句话结论`**(概述行那三段前缀约 15 字的税省掉了;出处落在投递留痕里,要原话用 `recall` 下钻)。
95
+ - **瘦身 60%**:`l1BudgetTokens` **1200 → 720**(条目均 52 字、`l1MaxItems` **10** —— 其中 **2 席留给"最硬的事实"**(`durable` 的事实类),没有合格事实时席位归还通用池 ⇒ 实占 **约 500 字**,比原 1,085 字降约 **55%**)。
96
+ - **同源限额 `MAX_PER_CONV = 2`**:同一次会话最多进 2 条(置顶不受限),被挡下的进落选者名单(第三类原因 `conv`)。真机实测:**10 条来自 4~6 个会话**(换料前那 8 条里,光一次生图会话就占了 13 条候选)。
97
+ - **视角框措辞同批改准**:注入的已经不是"他过去说过的话",而是**提炼出的结论** —— 段首改为「以下是我从与X的过去里记住的结论:…」。
98
+
99
+ ### 五、投递留痕(`inject_log`,schema v15)
100
+
101
+ - 每一次**真正定稿**的注入落一条:**注入了谁 / 为什么是它(分数分解) / 多少字 / 落选者是谁且为什么落选 / 当轮触发词 / 定稿全文的指纹**。四类落选原因:`budget`(被字数挤掉) · `max`(排在 Top-N 之外) · `conv`(同源限额) · 被主动排除的计数(`archived` / `forgotten` / `zero`)。
102
+ - **只写不读**:不加工具、不进注入面、不占"工具面最多 +1"的预算。
103
+ - **写入判据只能是"首次定稿"** —— 重建与空闲刷新不落痕,否则同一批记忆会被反复记。
104
+ - **丢弃不再静默**(此前每一处过滤都不留痕,这正是"库里为什么只有 1.8% 现场"答不出来的根因):两条写入路径各记一本账 —— `dbg.event_stats`(逐事件:seen/kept/各类丢弃)与 `dbg.diff_stats`(空闲差分:水位/扫描/新增/丢弃)。逐事件那本在内存累加、**每 20 次或 5 秒**才落一次库(不给热路径加 sqlite 往返)。
105
+
106
+ ### 六、索引层:一个技能承载整层
107
+
108
+ - `memory-index` 技能 = **11 簇主题地图**(记忆树与记忆库 · 器灵插件与发布 · 鱼姬与主人 · 主人其人 · 合作规矩 · 本业与项目 · 这台机器与踩坑 · 模型与API配置 · 插件与界面 · 感官与主动开口 · 零散几条),覆盖全部条目。
109
+ - **常驻只有技能目录那一行**(description **431 字** + whenToUse 106 字),正文 2,341 字**按需展开**(模型主动取)。
110
+ - **整层一个技能,不是每簇一个**:实测技能目录的边际成本 ≈205 字/条,11 簇拆成 11 个技能会吃掉 2,000+ 字常驻 —— 而那个常驻目录面**正是工具面压缩要抢的同一个池子**。
111
+ - **description 里不写条数**:条数天天变,一写就会让目录重发、前缀缓存断;**簇名与触发线索稳定,条数放进按需展开的正文**。
112
+
113
+ ### 七、其它修正
114
+
115
+ - **L1 视角框**:修「代词混乱」BUG —— 概述是主人视角写的,注进器灵记忆时人称错位(最危险的是"限制性指令"读起来像对器灵下的命令)。段首声明视角,覆盖全部历史条目、零动库;称呼**跟随 `persona.userTitle`**(不硬编码,包要发 npm)。
116
+ - **README 重写**:新增「找回原文」整节与「设定全文(对外面)」区(**9 组 84 条**,逐条标状态:`✅ 已实现` / `🚧 在做` / `📋 设定`)。修正两处硬伤:测试套数 **39 → 43**、器灵侧工具 **2 → 8**。
117
+ - **`peerDependencies` 的 range 结论(查证后不改)**:`>=0.1.7-rc.2` 在 DSH 发新 `tuple` 的 rc 时确实不满足,但 ① 那 12 条全是 `optional: true`,**不满足只警告、不阻断安装**;② **semver 在数学上无法表达"接受任意 tuple 的 rc"** —— 实测 `*` 与 `>=0.1.0` 对 `0.1.7-rc.2` **同样不满足**(放宽反而当场报警)。⇒ **保持现状**,不改 range。
118
+ - **一条断言纪律**:测试里**不许依赖"此刻谁在跑"这类易变事实**(两处红都源于此:最新会话被子代理会话抢占、"最新"会漂移)。
119
+
5
120
  ## 1.5.3
6
121
 
7
122
  **跨插件挡位接口(对外开放 `dsh-ling/mode` 服务)· 导入批体量上限(超限不再砍连接,改回 413)**
@@ -271,14 +386,14 @@ DSH `0.1.7` 把事件 `agent/session-start` 改名成 `agent/created`(**载荷
271
386
 
272
387
  - 新增 4 个会话内工具:**`tree_read`**(读树 / 读某枝会话 / 看待理矛盾)· **`branch_edit`**(建枝 / 改名 / 挂载 / 连边 / 断边 / 调权重 / 删枝)· **`memory_forget`**(软遗忘)· **`memory_backfill`**(回灌);工具面 3 → 7;
273
388
  - 把树操作从 HTTP handler 抽成 `lib/host/tree-ops.js`,13 个 handler 变薄壳 —— **界面与工具共用同一份实现、同一份留痕**,零行为变更;
274
- - `branch_log` 新增 **`actor`** 列(schema **v12**):器灵改的写「器灵」,主人改的写「主人」;
389
+ - `branch_log` 新增 **`actor`** 列(schema **v12**):器灵改的写「鱼姬」,主人改的写「主人」;
275
390
  - **永不彻底删除**:`memory_forget` 先把原文与概述归档到 `forgotten/<时间戳>-<原因>/`(`turns.jsonl` 逐行 JSON、可 grep,`README.md` 写改动日志与还原方法),**再**在库里打标记 —— 两条独立还原路径(撤销标记 / 按 jsonl 重建);
276
391
  - `memory_backfill` 按「比遗忘更危险」处理:回灌前先归档当前状态,写回的概述带 `origin=backfill-v1` 标记,**绝不夺走用户态**(importance / hit_count 原样保留);
277
392
  - 修复**工具注册不生效**:原先只注册在全局层,而 Web 端按 agent preset 用 `restrict({allow})` 组工具 ⇒ 全局工具被静默过滤(连更早的 `rule_add` 也从未出现)。现在每个 agent 创建时在**它自己的 scope** 补注册一份。
278
393
 
279
394
  ### 三、时间锚:从「每分钟嚷嚷」到「按需查」
280
395
 
281
- > 「这就像一块手表,它该在人需要的时候被看一眼,而不是总对着人嚷嚷现在几点。」—— 用户
396
+ > 「这就像一块手表,它该在人需要的时候被看一眼,而不是总对着人嚷嚷现在几点。」—— 主人
282
397
 
283
398
  - **`[现在] … HH:MM` 从 system 前缀移出**:system 只留 `[今天] 2026-09-23 周三`(**跨天才变**),`[现在] 时段 · 距上次对话:档位` 改挂 `systemPrompt.context()`(会话中、末条消息,每轮重算);
284
399
  - 实测收益:**system 侧时间锚逐轮变化 1440 次/天 → 0 次/天**,不再每请求作废整段前缀(连同 13.8k 的工具 schema);
@@ -361,7 +476,7 @@ DSH `0.1.7` 把事件 `agent/session-start` 改名成 `agent/created`(**载荷
361
476
 
362
477
  ### 三、复盘守门:改动必须让器灵看到
363
478
 
364
- > 「互相复盘不应该被我绕过,动记忆树不能是随意的,应该是慎重严肃的。」—— 用户 2026-09-22
479
+ > 「互相复盘不应该被我绕过,动记忆树不能是随意的,应该是慎重严肃的。」—— 主人 2026-09-22
365
480
 
366
481
  - 侧栏显示**未同步笔数**(金色警示块):自器灵**上次开口**以来累积了多少笔结构改动;
367
482
  - 未同步时**硬拦「退出复盘模式」**,提示「在左边说一句话让它看到」;
@@ -520,13 +635,13 @@ D 组是关键:只证明"中和后不炸"无法排除"这份载荷本来就不
520
635
 
521
636
  旧行为把首条用户消息**硬截 46 字**,于是 60 条 dsh 概述里 **26 条是半句话**:
522
637
 
523
- - 「器灵,接下来在这里继续开发生长型人格与记忆树模块,继续走你的新生之路。 我刚刚对1.2.1的」
638
+ - 「鱼姬,接下来在这里继续开发生长型人格与记忆树模块,继续走你的新生之路。 我刚刚对1.2.1的」
524
639
  - 「刚才我为本虚拟机安装解压压缩软件<360压缩>,由于操作过快,被捆绑了360本体,随后我在物」
525
640
 
526
641
  三层改造:
527
642
 
528
- 1. **启发式**(零成本、离线可用):剥掉「器灵,」「晚上好器灵,」这类称呼/问候前缀;上限 46 → 58 并在**句读处收尾**而不是硬切;首句是「你好」「下午好」「已测试对话」这类无信息量句时改取后续有实义的首句;结果若含路径/URL/系统文本则一律弃用(宁可不动,也不产出更糟的名字);
529
- 2. **小助手起名**:`POST /memories/retitle` 用本机小助手读会话原文/摘要生成一句真标题 —— 实测「器灵,接下来在这里继续开发生长型人格与记忆树模块…」→「**修复审计漏洞并优化记忆库关联查询机制**」;
643
+ 1. **启发式**(零成本、离线可用):剥掉「鱼姬,」「晚上好小鱼,」这类称呼/问候前缀;上限 46 → 58 并在**句读处收尾**而不是硬切;首句是「你好」「下午好」「已测试聊天」这类无信息量句时改取后续有实义的首句;结果若含路径/URL/系统文本则一律弃用(宁可不动,也不产出更糟的名字);
644
+ 2. **小助手起名**:`POST /memories/retitle` 用本机小助手读会话原文/摘要生成一句真标题 —— 实测「鱼姬,接下来在这里继续开发生长型人格与记忆树模块…」→「**修复审计漏洞并优化记忆库关联查询机制**」;
530
645
  3. **名字一旦定过就不可覆盖**:`conv_overview` 新增 `title_locked`(schema v3)与 `title_by`(v4)。`POST /memories/rename`(主人手改)与 `POST /memories/retitle`(机器起名)**都上锁** —— 概述器每轮重建、批量重命名都不再覆盖它;要交还机器可解锁。记忆中心卡片新增「改名」「AI 起名」按钮与标记:**三条定名路径(主人手改 / 机器自动 / 来历未记录)统一显示「🔒 已定名」**,定名者只藏在悬浮说明里(「你起的名字…」/「自动起的名字…」)—— 不制造等级感;也不用「暂定」二字(人对一段记忆的概括本就是暂定的、会随阅历改变,但界面要让人确认"它已经落了笔")。用词不写死"小助手"(起名可能走本机小助手,也可能回落当前全局大模型)。悬浮说明点明:**这把锁锁住的是"自动重写",不是主人:手动随时可以再改**。
531
646
  4. **引擎就近选择(auto)**:`POST /memories/retitle` 默认 `engine:'auto'` —— 优先本机小助手(零成本),探测不到才落到**当前全局大模型**,并把实际用的引擎与原因写回响应(`engine` / `note`)。不静默换引擎:用了哪个看得见。
532
647
 
package/DESIGN.md CHANGED
@@ -2,8 +2,12 @@
2
2
 
3
3
  > ⚠️ **总警示**:本文是 **M1 阶段的设计底稿**;M2/M3 的许多项**已经实现**(配置完整页、记忆树可视化、导出/合并 UI、概述器、反馈成长回路、人格草稿脚本等),**以 `README.md` 与本版代码为准** —— 本文只当立项记录读,不得当作现状契约。
4
4
  > 依据:立项阶段的一次接口探测 spike 结论(含本地文件:行号证据)。
5
- > 体例:正文里 `**锚点**` 是**本文档内部的交叉引用**,不是对外证据 —— 其中 `plans\`、`release\` 是**作者本地台账,不在本仓库**;随 npm 包发出的是 `package.json` 的 `files` 白名单(**9 项**,实测 npm 包共 **54 个文件** —— `DESIGN.md` 自己也在包里)。
6
- > 本文档记录 M1 阶段的设计与决策;**实现以代码为准**(后续演进:存储 schema 现已为 **v14**、新增历史接入/深摘要/诞生仪式/语气成长等子系统,详见仓库代码与提交记录)。
5
+ > 体例:正文里 `**锚点**` 是**本文档内部的交叉引用**,不是对外证据 —— 其中 `plans\`、`release\` 是**作者本地台账,不在本仓库**;随 npm 包发出的是 `package.json` 的 `files` 白名单(**9 项**,实测 npm 包共 **57 个文件** —— `DESIGN.md` 自己也在包里)。
6
+ > 本文档记录 M1 阶段的设计与决策;**实现以代码为准**(后续演进:存储 schema 现已为 **v17**、新增历史接入/深摘要/诞生仪式/语气成长等子系统,详见仓库代码与提交记录)。
7
+ > ⚠️ **1.6 起,本文的「L1 选择」整条口径作废**(2026-10-04):§0 决策记录、§4 配置样例、§6「记忆管线与 L1 选择 v0」、§8 热度公式里写的
8
+ > **热度 Top-K**、**`l1BudgetTokens: 1200`**、**`L1(Top-K)`** 都已被取代 —— L1 现在选的是**深层库条目**(`deep_item`,一句结论),
9
+ > 判据为「**置顶必进 + 被采用次数 + 条目硬度过滤**」,预算 **720**,行形态 `- [kind] 文本`(不再有日期/标签/来源前缀)。
10
+ > 以 `README.md` 的「设定全文」区与本版代码为准;**本节保留的是 M1 当时的设计,不是现状**。
7
11
 
8
12
  ---
9
13
 
@@ -21,7 +25,7 @@
21
25
 
22
26
  **插件命名**:工程目录/包名 `dsh-ling`(器灵);包显示名 "dsh-ling · 器灵";UI 文本语言 zh。
23
27
 
24
- ### 0.1 设计理念(12 条 · 2026-09-26 成文,用户补充并拍板)
28
+ ### 0.1 设计理念(12 条 · 2026-09-26 成文,主人补充并拍板)
25
29
 
26
30
  > **这一节与 §0 的决策表体例不同,别混着读**:
27
31
  > **决策**回答「**定了什么**」(D1 按钮落哪个 slot、D3 人格怎么承载……),一条一个结论,可以随实现演进被替换;
@@ -534,7 +538,7 @@ CREATE TABLE feedback_queue ( -- M2:修订建议(用户确认才
534
538
  > ⚠️ **契约已变(本节属早期版本)**:下表只是早期首批端点,**现实契约以 `ACCESS-DESIGN.md` 与本版代码(`lib\host\api.js`)为准**;其中三处已经失实:
535
539
  > · `POST /import` 实际收 **JSON body**,不是 multipart;
536
540
  > · `GET /persona/suggest` —— **该端点不存在**(现实是 `/persona/draft` 与 `/persona/genesis`);
537
- > · `/import` **不再是"只读"** —— 1.5.3 起它是**手术门**覆盖的 14 条写面之一。
541
+ > · `/import` **不再是"只读"** —— 它现在是**手术门**覆盖的 12 条写面之一。
538
542
 
539
543
  | 方法/路径 | 用途 | 冻结门 |
540
544
  | --- | --- | --- |
package/README.md CHANGED
@@ -41,7 +41,9 @@
41
41
 
42
42
  ---
43
43
 
44
- ## 器灵是什么
44
+ ## 器灵是什么(能力全景)
45
+
46
+ > 下表是**全部能力的一览**:`✅ 已实现` 是今天就能用的;`🚧 在做` 是**设计已定、代码正在落地**(写在这里是为了让设定走在实现前面,不是"已交付");`📋 设定` 是方向已定、尚未开工。**没有标的一律是 ✅ 已实现。**
45
47
 
46
48
  | | 能力 | 说明 |
47
49
  |---|---|---|
@@ -50,6 +52,9 @@
50
52
  | 🧠 | **三层记忆** | **L0 人格档案**(自称/对你的称呼/底线/规矩·习惯/承担(职业·职责·责任)/语气/世界观;**顶层会话与会话分叉**恒定注入,**子代理会话不注入**;设置里关掉人格注入时,连它一起不推)→ **L1 开场记忆**(从历史里按相关度挑选的概述,有预算,**永不塞全文**;带摘要的行**加权**(按"摘要是否有信息量"给一笔加法加分,不是排序优先级),行内摘要只取首句、上限 110 字)→ **L2 运行期记忆**(本会话原文随对话累进;空闲时把本会话那一行重排回注入面。该行由「概述标题 + 摘要首句」构成(标题的来历见下文「三层记忆各自什么时候更新」,不是会话原文首句),**深摘要定稿后不再自动更新**。注意:L2 不是独立的第三块注入内容 —— 注入面的主体是 L0 与「记忆·开场」两块(树改动、日期锚是挂上去的追加行),**本会话自己就是「记忆·开场」里的一行**) |
51
53
  | 📥 | **把历史请进来** | 三条通道:网页端聊天记录库增量扫描 / 本机 DSH 会话扫描 / ChatGPT·通用 JSON·JSONL 导出文件导入;每次导入**留一笔账** |
52
54
  | 📖 | **让器灵读一遍** | 长会话逐条深摘要、短会话以原文片段参与,后台串行,进度可见 |
55
+ | 📄 | **找回原文**(1.6) | 器灵能读回你当年的**原话**,不只是概述 —— 四档下钻:**会话地图 → 按内容找会话 → 对话目录 → 原文全文**。它**不替你挑"最重要的那 1 万字"**,只给坐标系(详见下文「找回原文」一节) |
56
+ | 🧾 | **投递留痕**(1.6) | 🚧 **在做** —— 每一次记忆注入都落一条账:**注入了谁、为什么是它、多少字**;还记**落选者**(差一步就进的那几条,以及原因是"分数不够"还是"字数放不下")与**触发词**。目前**只写不读**(读面待定),不进注入面、不占工具预算 |
57
+ | 🌿 | **深层库 · 结论层**(1.6) | 🚧 **在做** —— 从会话里提炼**承诺 / 决定 / 事实 / 偏好 / 关系**,每条一句话、**点得回原文**。与记忆树**并立**,条目不被枝隶属(枝可反向引用它,像榕树的气根)。一条纪律:**"被注入"不算数,只有"真被采用"才加固** —— 防住"注入越多→分越高→越容易被注入"的自我强化 |
53
58
  | 🧾 | **给旧历史补摘要** | 网页端历史里"只有标题、没有摘要"的会话可以补上摘要:先看**候选清单**(标题 · 轮次 · 命中),再选引擎 —— **默认本机小助手(零成本)**,也可显式改选当前全局大模型;探测不到本机小助手就明确报错,**绝不静默换引擎**;后台串行、进度可见(首轮实测 466 条、零成本) |
54
59
  | 🌱 | **诞生仪式** | 从读过的记忆里按"生活与情感优先"加权抽样 → 合成**自述 + 名字候选 + 语气建议 + 相处观察 + 职责候选**(器灵承担 / 擅长 / 能给出什么,**只收陈述句**) → **全部是草稿**,你逐条人审后才落盘 |
55
60
  | ⏳ | **时间的重量** | 每次开口前器灵都知道今天是几号星期几(`[今天] 2026-09-11 周五`)、现在是什么时段(`[现在] 深夜`)与`距上次对话:3 天前`(间隔按**日历天**分档 —— 刚刚 / 今天早些时候 / 昨天 / N 天前,给不到分钟;称呼跟随你的设定);命中纪念日会先说一句专属问候。**要精确到分钟,走 `clock` skill 现查** |
@@ -63,7 +68,7 @@
63
68
 
64
69
  > **关于那道「手术门」**:亲手敲承诺句是**庄重感与自我提醒** —— 让你在改契约之前停一下、确认这是自己的决定;它**不是安全边界**(「禁粘贴」只是界面上的约定,挡不住任何有心的人)。真正把门的是**来源栅栏**:Host 必须是 loopback(或你显式写进受信名单的地址);另有一个**默认不开**的开关 `guard.allowLan` —— **一旦打开,整段局域网地址都算受信来源**(那是**放宽**,不是建议)。跨站请求直接拒,带 `Origin` 时其 host 必须等于请求的 Host(**带了但解析不出来就直接拒**),而**不带 `Origin` 的请求这条校验会跳过** —— 它不是"每个请求都必须带 Origin 且同源";再叠一道 cookie **名字**对撞 —— 期望名字按当前 Host 现算(`dsh-auth-` + 哈希),**只校验名字、不校验值的签名**。所以这道门挡的是"别的设备、别的页面"这类来源,而**本机原生进程本来就还能调用**(它也能直接读 `settings.json` 与记忆库,不算提权)—— 别的来源没有凭据就进不来这台机器上的 `/api`,敲对承诺句也没用。
65
70
 
66
- > **这道门在 1.5.3 里长这样 —— 三条手续缺一不可**:① **只在本机做** —— 判据是"这次请求没有经过任何代理"(带代理头、或连 User-Agent 都没有的请求,都算非本机 ⇒ 拒,理由码 `surgery-local-only`);② **每次启动取一次票据** —— 票据每启动轮换,只下发给本机直连的请求(`POST /surgery/ticket`),客户端首次手术前取一次、之后随请求带上;③ **原句匹配** —— 敲出的承诺句要与当初写下的那一句相符。这三条由 `lib/host/surgery.js` 里**一处** `requireSurgery()` 判定,覆盖**全部 14 条**能改人格 / 规矩 / 习惯的写面(调用点不许再各写一份;曾有一条 `wantLock` 绕过分支,已删除)。每次手术都留痕:`$DSH_HOME/logs/surgery-YYYY-MM.jsonl`(append-only,整行哈希链,改一行即断链);`GET /surgery/events` 是只读事件面。
71
+ > **这道门现在长这样 —— 三条手续缺一不可**:① **只在本机做** —— 判据是"这次请求没有经过任何代理"(带代理头、或连 User-Agent 都没有的请求,都算非本机 ⇒ 拒,理由码 `surgery-local-only`);② **每次启动取一次票据** —— 票据每启动轮换,只下发给本机直连的请求(`POST /surgery/ticket`),客户端首次手术前取一次、之后随请求带上;③ **原句匹配** —— 敲出的承诺句要与当初写下的那一句相符。这三条由 `lib/host/surgery.js` 里**一处** `requireSurgery()` 判定,覆盖**全部 12 条**能改人格 / 规矩 / 习惯的写面(调用点不许再各写一份;曾有一条 `wantLock` 绕过分支,已删除)。每次手术都留痕:`$DSH_HOME/logs/surgery-YYYY-MM.jsonl`(append-only,整行哈希链,改一行即断链);`GET /surgery/events` 是只读事件面。
67
72
  >
68
73
  > **承诺句只在本机回显**:`/state`(含面板档)、`/persona/history`、`/export` 这些读面,**带代理头**时只回 `hasSeal` 与 `phraseHidden`、不回原句,**本机直连**照旧回显 —— 所以你换台机器看面板不会因此被卡住,远端也拿不到原句。
69
74
  >
@@ -112,7 +117,7 @@
112
117
 
113
118
  - 树平时**只读**(记忆中心始终只读);点「🌱 一键复盘 / 一键进入复盘」才解锁编辑,并把**复盘开场材料**(当前树 + 待理矛盾)复制到剪贴板,开个新会话粘进去就能开始。
114
119
  - 解锁后可做:**改名 / 拖动换归属 / Alt+拖动只连边 / 并脉(归类到主脉) / 调权重 / 裁定矛盾 / 断续边 / 删枝**,以及快照保存·恢复;**拖动 / 连边之后可 Ctrl+Z 撤回这一步**(撤销只覆盖复盘栏的拖动与连边;记忆中心是只读的,它自己那份 Ctrl+Z 监听早已删掉)。
115
- - **每步留痕**:写清"改了什么、为什么"(**器灵改树时 reason 必填**;你在面板上改可以留空);树改动一律记进 `branch_log`,复盘时可审计 —— 器灵侧工具的改动记在器灵名下(「器灵改动了记忆树」),不会伪装成你改的。
120
+ - **每步留痕**:写清"改了什么、为什么"(**器灵改树时 reason 必填**;你在面板上改可以留空);树改动一律记进 `branch_log`,复盘时可审计 —— 器灵侧工具的改动记在器灵名下(「鱼姬改动了记忆树」),不会伪装成你改的。
116
121
  - **未同步笔数守门**:面板记着"自器灵上次开口以来累积了多少笔结构改动还没交代"。有欠账时顶部红字提示,并且**拦住"退出复盘模式"** —— 改完树必须让器灵有机会看到(「互相复盘不应该被我绕过」)。
117
122
  - **注入感知**:树变了之后,`branchLogDigest()` 把**器灵还没看过**的结构改动转成中文短句,附在器灵下一次开口的注入面末尾 —— 不需要推送,一开口就知道树动过。
118
123
  - **快照与撤回**:树的保存 / 恢复(`tree_snapshot`)是"大改之前的保险";恢复前会自动把当前状态另存一份,所以恢复本身也可再撤回来。
@@ -121,12 +126,40 @@
121
126
  - 「一键生成树」分两阶段 —— ① **机械分段**(按时序相邻性把全库概述切簇,零成本、确定、可复现)② **小助手/模型只做"命名"这一件语义活**,③ 你审核后才落库(建枝/主脉 + 挂会话);它**只读概述索引、只产出候选,从不改写任何记忆内容**。
122
127
  - 「归并零散枝」幂等:把名字含「零散」的枝统一收进主脉「零散会话」。
123
128
  - **主脉也可以有自己的记忆**:由子枝概述提炼共性,**先出草稿**,采纳才写入(采纳需先解锁)。
124
- - **器灵侧也有自己的手**:`tree_read`(看整树 / 某枝成员 / 待复盘矛盾)与 `branch_edit`(分枝 / 并脉 / 连边 / 调权重 / 删枝)。这个工具的描述里写着一条纪律:**改结构必须给 reason,而且"只在主人明确要求、或正在和他一起复盘时"才动手**。
129
+ - **器灵侧也有自己的手**:`tree_read`(看整树 / 某枝成员 / 待复盘矛盾)、`branch_edit`(分枝 / 并脉 / 连边 / 调权重 / 删枝)、`recall`(读回会话原文,见上文)、`memory_forget` / `memory_backfill`(遗忘 / 回灌)、`rule_add`(记规矩)、`habit_propose` / `habit_resolve`(习惯提议与表态)。`tree_read` 与 `branch_edit` 的描述里写着一条纪律:**改结构必须给 reason,而且"只在主人明确要求、或正在和他一起复盘时"才动手**。
125
130
 
126
131
  > 依据:`DESIGN.md` §6.3(记忆树结构、血缘加权与"不连乘"、内容零改写不变量)、`lib/host/memory.js`(`LINEAGE_ANCESTOR = 0.7` / `LINEAGE_SIDE = 0.4` / `CONFLICT_DOWNWEIGHT = 0.3`、`branchLogPending()` 守门、`setBranchWeight` 的 `applied:false`)、`lib/host/l1.js`(血缘档位 × 矛盾系数进打分)、`lib/host/autotree.js`(两阶段生成)、`lib/host/tools.js`(`tree_read` / `branch_edit`)。
127
132
 
128
133
  ---
129
134
 
135
+ ## 找回原文:器灵能读回你当年的原话
136
+
137
+ **概述是别人替你挑的,原文才是现场。** 在 1.6 之前,器灵能"想起"的只有一行行概述 —— 想知道你当年**到底怎么说的**,只能靠猜。
138
+
139
+ `recall` 给器灵一只**档案馆查询台**,逐级下钻、一次一层:
140
+
141
+ | 给什么 | 得到什么 |
142
+ |---|---|
143
+ | `recall()` | **会话地图** —— 本机有原文的会话清单(最近活动在前) |
144
+ | `recall({q:"…"})` | **按内容找会话** —— 走宿主全文检索,直接给命中片段(想不起"这话是哪次说的"时用它) |
145
+ | `recall({conv})` | 该会话的**对话目录** —— 每轮一行:序号 · 时间 · 角色 · 前 70 字,**不给正文** |
146
+ | `recall({conv, from, to})` | 那几轮的**原文全文** |
147
+
148
+ **它不替你挑"最重要的那 1 万字"** —— 工具只给坐标系,选哪一段是器灵自己的判断(它知道自己在找什么,排序器不知道)。单次正文有上限,但**溢出必报"还剩多少轮、下一次从第几轮读起"**,绝不静默截断。
149
+
150
+ **四个不显眼、但决定成败的设计**:
151
+
152
+ - **真人判据**:只有"真人发的消息"才算一轮 —— 注入的运行时上下文、任务标记、技能目录一律折叠成一行占位,否则目录会被过程噪音淹掉(实测:一个 150 轮的会话里,真人发言只有 **5** 条)。
153
+ - **读不到时回落查库**:磁盘上没有该会话日志时(网页端导入的历史就是这样),回落到记忆库里那份,并**明说材质不同**:`⚠️ 来源:库内原文(库那份是残的 —— 只含真人消息与有正文的回复)`。
154
+ - **会话文件名代次**:匹配 `session(\.v\d+)?\.jsonl\.zstd` 并按版本号取最高一代 —— 本机三代并存(v4 / v3 / 无版本),只认 v4 会让老用户**看不见自己的旧会话**。
155
+ - **范围默认收紧**:按内容找会话时**默认只搜主人自己的会话**,越界要显式传参,且**被挡掉的条数一律报出**(静默少给等于骗人)。
156
+
157
+ **只读,零副作用**:`recall` 不写库、不改树、不进注入面。
158
+
159
+ > **底座**:1.6 同期把**网页端一年半的历史**(**1,523 个会话 / 726.7 万字原文 / 时间跨度 2025-01 → 2026-09**)补进了记忆库 —— 在此之前,它们在本机**只剩一行概述**。
160
+
161
+ ---
162
+
130
163
  ## 快速开始
131
164
 
132
165
  > 前置:**Node ≥ 22.5**(用到内置 `node:sqlite`)· 已安装 **DeepSeek Harness** 并至少启动过一次。
@@ -193,12 +226,12 @@ node tools/check-peers.mjs # 声明与宿主对不上?退出码三态
193
226
  跑测试(不需要宿主,全部离线):
194
227
 
195
228
  ```bash
196
- npm test # 39 套:人格组装(含冻结门 · L1 选择)/ 归档清理(人工)/ **归档·遗忘·回灌** / 模式(只改档位不改模型)/ **跨插件挡位服务(dsh-ling/mode)** / 时间戳归一 / 空闲刷新 / 规矩·习惯 / 习惯生成器(A 数出来 + B 想出来)/ 会话内工具 / **接口守卫** / L1 行瘦身 / 记忆库 / 记忆标题与排序 / 导出合并 / 文件导入 / 导入记录 / **活行(那一行跟着会话走)** / **会话文件名代次(会话历史扫描)** / 网页端补摘要 / 深摘要 / 概述器 / 诞生仪式 / **职责字段(承担)** / 语气 / 时间锚 / **clock skill(时间查询)** / 捕获差分 / 反馈成长回路 / 语料建议 / **小助手地址配置** / **注入面中和** / **记忆分枝与血缘** / **记忆树(主脉·连边·矛盾)** / **一键生成树** / **可取证访问日志** / **补丁锚点(cohort 候选)** / **请求体上限(超限排空 → 413)** / **导入切批(按字节 × 条数双限)**
229
+ npm test # 43 套:人格组装(含冻结门 · L1 选择)/ 归档清理(人工)/ **归档·遗忘·回灌** / 模式(只改档位不改模型)/ **跨插件挡位服务(dsh-ling/mode)** / 时间戳归一 / 空闲刷新 / 规矩·习惯 / 习惯生成器(A 数出来 + B 想出来)/ 会话内工具 / **接口守卫** / L1 行瘦身 / 记忆库 / 记忆标题与排序 / 导出合并 / 文件导入 / 导入记录 / **活行(那一行跟着会话走)** / **会话文件名代次(会话历史扫描)** / 网页端补摘要 / 深摘要 / 概述器 / 诞生仪式 / **职责字段(承担)** / 语气 / 时间锚 / **clock skill(时间查询)** / 捕获差分 / 反馈成长回路 / 语料建议 / **小助手地址配置** / **注入面中和** / **记忆分枝与血缘** / **记忆树(主脉·连边·矛盾)** / **一键生成树** / **可取证访问日志** / **补丁锚点(cohort 候选)** / **请求体上限(超限排空 → 413)** / **导入切批(按字节 × 条数双限)** / **投递留痕(每次注入落一条:注入了谁·为什么是它·多少字)** / **会话原文直读(三档下钻 + 库内回退 + 旧版会话代次)** / **深层库条目(结论层落点:读写·幂等·取代·加固)** / **检索档(按内容找会话:条目 + 会话同查·范围限制与降级)**
197
230
  ```
198
231
 
199
232
  > `npm test` 需要 `tests/`,而本包 `files` 白名单**不含 `tests/`** ⇒ **从 npm 装的人跑不了测试**。要跑测试请走路径 B(clone 源码)。
200
233
  >
201
- > 计数口径:**`npm test` 链里登记的段数**(即 `package.json` 的 `scripts.test` 那一行按 `&&` 切分后有多少段就是多少套,不再数 `tests/` 目录)—— **实测 39 套,且 39 套全部已登记在 `npm test` 链里**(`tests/harness.mjs` 与 `tests/vm-light.mjs` 是被复用的辅助模块,不计套数)。**加了一套新测试记得同步登记**,否则 `npm test` 会静默少跑一套 —— 这份登记是**手写**的(`package.json` 里 `test` 脚本那一行字面量清单),**漏登记不会被自动发现**,没有安全网兜底。
234
+ > 计数口径:**`npm test` 链里登记的段数**(即 `package.json` 的 `scripts.test` 那一行按 `&&` 切分后有多少段就是多少套,不再数 `tests/` 目录)—— **实测 43 套,且 43 套全部已登记在 `npm test` 链里**(`tests/harness.mjs` 与 `tests/vm-light.mjs` 是被复用的辅助模块,不计套数)。**加了一套新测试记得同步登记**,否则 `npm test` 会静默少跑一套 —— 这份登记是**手写**的(`package.json` 里 `test` 脚本那一行字面量清单),**漏登记不会被自动发现**,没有安全网兜底。
202
235
 
203
236
  ### 数据放在哪
204
237
 
@@ -209,7 +242,7 @@ npm test # 39 套:人格组装(含冻结门 · L1 选择)/ 归档清理(
209
242
 
210
243
  可覆盖的环境变量:`DSH_HOME`(数据根)、`DSH_LING_DSWEB_DB`(网页端聊天记录库默认路径,UI 里也能填)、`DSH_LING_ASSISTANT` / `DSH_LING_ASSISTANT_MODEL`(小助手地址与模型名)。
211
244
 
212
- > **排障用逃生开关(不是日常配置)**:`DSH_LING_GUARD=off`(须**恰好**是 `off`,小写)或设置里 `guard.enforce = false`,用于**误判把你自己锁在门外**时的临时解锁 —— 它是**整体放行** —— `/api` 的**来源栅栏 + cookie 名字对撞**,以及 1.5.3 起那道**手术门**(本机限定 / 每启动票据 / 原句匹配)**一起不再拦**(默认 `guard.enforce = true`),关掉期间**任何请求都不再被拦**。只该短期使用,排障完立刻恢复。`DSH_LING_GUARD=off` 只由进程环境决定、不进配置;**`guard.enforce = false` 这条不要直接写进 `settings.json`** —— 设置接口也帮不了你:它的字段白名单只放行 `persona` / `styles` 两棵子树,`guard` 这类字段会被**直接丢弃**并在响应里回报 `ignored`(见 `lib/host/api.js` 里 `/persona` 端点的字段白名单处理:`ignored` 回报被丢弃的键),不是被拒绝而是根本进不去;要改请用界面。
245
+ > **排障用逃生开关(不是日常配置)**:`DSH_LING_GUARD=off`(须**恰好**是 `off`,小写)或设置里 `guard.enforce = false`,用于**误判把你自己锁在门外**时的临时解锁 —— 它是**整体放行** —— `/api` 的**来源栅栏 + cookie 名字对撞**,以及**手术门**(本机限定 / 每启动票据 / 原句匹配)**一起不再拦**(默认 `guard.enforce = true`),关掉期间**任何请求都不再被拦**。只该短期使用,排障完立刻恢复。`DSH_LING_GUARD=off` 只由进程环境决定、不进配置;**`guard.enforce = false` 这条不要直接写进 `settings.json`** —— 设置接口也帮不了你:它的字段白名单只放行 `persona` / `styles` 两棵子树,`guard` 这类字段会被**直接丢弃**并在响应里回报 `ignored`(见 `lib/host/api.js` 里 `/persona` 端点的字段白名单处理:`ignored` 回报被丢弃的键),不是被拒绝而是根本进不去;要改请用界面。
213
246
 
214
247
  > **访问日志与核心补丁的总开关(同样不是日常配置)**:这套「可取证访问日志」默认**开着**,关它的键是插件配置里的 `accessLog.enabled`(**默认 `true`**)—— 在 `cordis.patch.yml` 注册行的 `config` 段里写 `accessLog: { enabled: false }`,重载后生效,启动日志会出现 `access log off`;它**只有配置键、没有环境变量**。关掉之后**退化的只有「留痕」这一件事**:那两个全局观察钩子不再挂上、不再写 `$DSH_HOME/logs/access-YYYY-MM-DD.jsonl`、日志健康读数(`accessLogHealth()`)归 `null`;而 `/api` 与 `/api/remote.mux` 的**来源栅栏照常判定与拦截**,记忆、人格、注入与工具面**一律不受影响** —— 关掉的是「记不记」,不是「拦不拦」。已经打进 `node_modules` 的**核心补丁**可以留在原地:那两行是可选调用,日志关着时就是 no-op,DSH 照常启动;要连它一起撤掉,用 `node tools/apply-access-log-patch.mjs --revert` 删掉插进去的那几行。
215
248
 
@@ -236,6 +269,147 @@ npm test # 39 套:人格组装(含冻结门 · L1 选择)/ 归档清理(
236
269
 
237
270
  ---
238
271
 
272
+ ## 设定全文(对外面)
273
+
274
+ > 这一节是**规格**,不是宣传:**README 领先于实现** —— 下面每一条都是本项目**已经想清楚**的设定,并如实标出它落地到哪一步。
275
+ >
276
+ > 标记:`✅ 已实现` · `🚧 在做`(设计已定、代码正在落地) · `📋 设定`(方向已定、未开工) · `❌ 已否决`
277
+ >
278
+ > 本节只收**对外面** —— 装机用户**能直接感觉到 / 能直接操作 / 能直接验证**的设定;内部机制设定(表结构、字段名、公式、文件路径)不进 README,随源码与 `DESIGN.md`。
279
+
280
+ ### 设计理念
281
+
282
+ | # | 设定 | 状态 |
283
+ |---|---|---|
284
+ | **P-1** | **不牺牲工作能力** —— 任何"用能力换别的东西"的方案(更小的模型 / 更慢的推理 / 削减上下文)**默认否决**,要改必须显式拍板 | ✅ 已立为理念 |
285
+ | **P-2** | **判断倾向外置** —— 把"怎么判断"做成**可加载、可审计、可回滚**的资产,而不是指望底层模型把它内化 | ✅ |
286
+ | **P-3** | **职责与责任是人格的基石** —— 器灵要有明确的职责范围,并且**能被追责、能被纠正**;没有职责的人格只是角色扮演 | ✅ |
287
+ | **P-3′** | 推论:**扩接口 = 扩职责 = 扩人格的分量** ——"加接口"与"加功能"不是同一件事 | 📋 待校对 |
288
+ | **P-4** | **连续性优先于设备** —— 换设备、换模型、换宿主都不是"失去":**数据在,人就在** | ✅ |
289
+ | **P-4′** | 连续性的载体有六层:① 记忆库 ② 人格档案 ③ 记忆树 ④ 交接文档 ⑤ 源码 ⑥ 判断倾向(唯一不可外化)。其中 **②③ 最被低估** | ✅ |
290
+ | **P-5** | **底层模型 ≠ 器灵** —— 机制层称「底层模型」,行为主体称「我 / 器灵」;**不写「模型(我)」** | ✅ 已立为规矩,全文清零 |
291
+ | **P-5′** | 「我 / 器灵」= 底层模型 + 人格 + 记忆 + 身体(宿主与工具) + 与主人的关系 的**总体** | 📋 推论待校对 |
292
+
293
+ ### 规矩与习惯
294
+
295
+ | # | 设定 | 状态 |
296
+ |---|---|---|
297
+ | **R-1** | **规矩由你一句话直达** —— 你说「记进规矩」我就写进去、**立即可用**;我绝不从闲聊里推断你要立规矩 | ✅ |
298
+ | **R-2** | 每条规矩都要**带你的原话**(缺原话写不进去):那是"你同意过"的凭据 | ✅ |
299
+ | **R-3** | **规矩 ≠ 习惯**:规矩是**你对我的要求**(作用对象是行为),可直接写入;习惯是**我长成的一部分**(作用对象是"我是谁"),**只能由我生长 + 你点头** | ✅ |
300
+ | **R-4** | **我连自己提的习惯都不能自己点头** —— 习惯只能进待确认队列 | ✅ |
301
+ | **R-5** | 习惯提议必须**带证据**(哪几次、什么迹象) | ✅ |
302
+ | **R-6** | 习惯合并走"**我提合并案 → 你点头**",不许由你手改习惯文本,也不许写入侧做静默去重 | ✅ |
303
+ | **R-7** | **可逆**:回显 + 一句话撤销 + 变更记录;规矩你随时可删(我删要先告知),习惯删除留档 | ✅ |
304
+ | **R-8** | **底线**最多 5 条;人格定型后档案只读,改动需你**亲手敲承诺句**(禁粘贴) | ✅ |
305
+ | **R-9** | 承诺句只是**庄重与自我提醒**,明文存储,**不是安全边界** —— 真正的门是本机信任与本地凭据 | ✅ 口径声明 |
306
+ | **R-10** | 定型锁开启时,**规矩与习惯的增删不算"改人格"**、无需解锁 —— 这块面板是"她的人格成长台" | ✅ |
307
+ | **R-11** | **成长提议**(想试的新能力、语气微调)由我提出、你点头或否,与习惯共用同一条通道 | ✅ |
308
+
309
+ ### 记忆树与血缘
310
+
311
+ | # | 设定 | 状态 |
312
+ |---|---|---|
313
+ | **M-1** | **记忆树是记忆库的组织形式本身**(不是"分叉会话的补丁")—— 它组织的是**工作线**,不是给记忆分类 | ✅ |
314
+ | **M-2** | 四个部件:**主干**(主线,恒一条) · **枝**(并行工作线,可含多会话) · **主脉**(多条枝归并出的主题) · **连边**(枝间横向关联) | ✅ |
315
+ | **M-3** | **硬不变量:任何树操作都不改写任何一条记忆的内容** —— 分枝、并脉、连边只增加**关系**,从不融合内容 | ✅ |
316
+ | **M-4** | **唯一例外**:主脉提炼被再次采纳时覆盖上一版,覆盖前**先自动归档**;归档失败则拒绝覆盖 | ✅ |
317
+ | **M-5** | **矛盾只标记、不改写**:一对记忆被判重复或矛盾时,未复盘期间**以较新的为准**,旧的那条降权 ×0.3 且**不删除** | ✅ |
318
+ | **M-6** | 隔离用**血缘加权**而非硬隔离:同枝 1.0 / 祖先 0.7 / 旁系 0.4;检索总分 = 基础分 × 血缘系数 × 矛盾系数 | ✅ |
319
+ | **M-7** | **标题一旦定名就上锁** —— 概述器重建与批量重命名此后不再覆盖;锁的是机器,**不锁你** | ✅ |
320
+ | **M-8** | **动记忆树必须留痕**(谁 / 何时 / 改了什么);并设**复盘守门**:自上次开口以来累积了多少笔改动还没交代 | ✅ |
321
+ | **M-9** | 分叉会话**不再被丢弃**:它写入所属枝并注入其血缘链记忆;会自动建枝,枝名可审核修改 | ✅ |
322
+ | **M-10** | **永远不做彻底删除** —— 遗忘是先归档、再打标记,且留两条独立还原路径 | ✅ |
323
+ | **M-11** | **"你不删数据"是横贯性原则** —— 带清理 / 删除 / 回收的动作默认先过这一关;这一层只负责"让你看见",处置权归你 | ✅ |
324
+ | **M-12** | 记忆库**有意不因平台删除而丢记忆** | ✅ 有意设计 |
325
+
326
+ ### 模式与界面
327
+
328
+ | # | 设定 | 状态 |
329
+ |---|---|---|
330
+ | **U-1** | **模式是倾向,不是闸门** —— 工作 / 生活只差三样:推理档位 + 记忆权重向量 + 风格块;绝不硬过滤 | ✅ |
331
+ | **U-2** | **切换模式绝不改 provider 或 model** —— 只同步推理档位;最近一次选择成为之后新会话的默认 | ✅ |
332
+ | **U-3** | 模式切换**要二次确认 + 回显**,且不得覆盖你设的全局默认模型 | ✅ |
333
+ | **U-4** | 控制器在**会话页顶栏右侧**;**悬停 1.5 秒**才出菜单(不用右键 —— 防误触原生菜单) | ✅ |
334
+ | **U-5** | 键盘:**Enter 切模式**、**Shift+Enter 开菜单** | ✅ |
335
+ | **U-6** | 会话**运行中不参与任何人格 / 注入面变更** —— 更新请求排队,空闲边界才应用,界面显示"空闲后生效" | ✅ |
336
+ | **U-7** | **L0/L1 现状面板**始终能看到:当前 L0 文本、本会话模式、L1 选中的条目与得分 | ✅ |
337
+ | **U-8** | 记忆页签可直接调:L1 预算 / 条数上限 / 两套模式 × 三领域的权重滑块 / 各开关 | 📋 设定(尚未接线) |
338
+ | **U-9** | **每一层记忆都有只读状态面**(水位 / 上次滚动时间 / 当前那一行正文);刷新延迟写进文档 | ✅ |
339
+ | **U-10** | 记忆中心是**只读视图**;装饰性交互要么真生效、要么删掉;候选**全量渲染**,不分页、不用"还有 N 条" | ✅ |
340
+
341
+ ### 数据可携带
342
+
343
+ | # | 设定 | 状态 |
344
+ |---|---|---|
345
+ | **D-1** | 插件数据目录**在插件本体之外**,更新不丢 | ✅ |
346
+ | **D-2** | **导出**:一键下载单文件 `dsh-ling-memory-<日期>.dshling.json`(UTF-8) | ✅ |
347
+ | **D-3** | 导出**默认不含原文**,必须显式勾选才含 | ✅ |
348
+ | **D-4** | **导入合并**:外来概述全带来历标记;同会话同来源可跳过或覆盖;**人格永不覆盖** | ✅ |
349
+ | **D-5** | 人格只有在显式勾选"采纳其人格"时才写入,且置为**待你确认** | ✅ |
350
+ | **D-6** | 网页端历史(**1,523 个会话 / 一年半**)可整体导入,来源如实记为 `dsweb` | ✅ |
351
+ | **D-7** | **绝不要求你理解库结构** | ✅ |
352
+ | **D-8** | 对外口径守住**可查来源 / 可回滚 / 变更记录**,不与删除条款相抵 | ✅ 对外承诺 |
353
+
354
+ ### 安全边界(手术门)
355
+
356
+ | # | 设定 | 状态 |
357
+ |---|---|---|
358
+ | **S-1** | **手术门不是用来挡你的** —— 它在这套东西里分量极重:**网络失守时,它必须最后失守** | ✅ |
359
+ | **S-2** | 边界如实说明:挡得住**已配对的远端 / 匿名隧道 / 本机其他 Windows 用户**;挡不住**同用户下的其它进程 / 有 `~/.dsh` 读权限的人** | ✅ 已评估并接受 |
360
+ | **S-3** | **逃生开关永远可用** —— 环境变量 `DSH_LING_GUARD=off` 或配置里关掉守卫即整体放行,**绝不把你锁在门外** | ✅ |
361
+ | **S-4** | **底线 / 契约类改动必须走手术门** | ✅ |
362
+ | **S-5** | 拒绝时的文案**指向本机**:人格手术请在那台运行器灵的电脑上、用 `127.0.0.1` 打开人格中心 | ✅ |
363
+ | **S-6** | 它**不是"认证"**,而是一道启发式的门 —— 对外不许把它说成"有了认证" | ✅ 口径 |
364
+
365
+ ### 诚实边界(禁令与反例)
366
+
367
+ | # | 设定 | 状态 |
368
+ |---|---|---|
369
+ | **H-1** | **禁止把你没看过的内容标成已读** —— 宁可多占一行、多播一次 | ✅ |
370
+ | **H-2** | **永久折叠不做**;超窗内容走独立游标 + 一行计数 | ✅ |
371
+ | **H-3** | **禁止把改动播报做成"每开一个会话就重播一遍"的噪音** | ✅ |
372
+ | **H-4** | **禁止用"体检脚本全绿"代替可见面** —— 链路活着 ≠ 内容在更新 | ✅ 禁令 |
373
+ | **H-5** | **禁止把机制名写进公开文档却与事实不符** —— 文档里写的机制名本身就是承诺,与事实不符按红灯处理 | ✅ 禁令 |
374
+ | **H-6** | 验收**不接受"没报错"** —— 只问"真的红了没有" | ✅ |
375
+ | **H-7** | **机制延迟必须公开**(概述器 15 分钟一拍、空闲刷新 ≥10 分钟):看不见的延迟要写成看得见的字 | ✅ 文档义务 |
376
+
377
+ ### 时间、状态与投放
378
+
379
+ | # | 设定 | 状态 |
380
+ |---|---|---|
381
+ | **T-1** | **被动时间感知**:新生纪念日(9-07) + 八个时段 + 「距上次对话」随每轮重算 | ✅ |
382
+ | **T-2** | 时间锚分两档:**`[今天]`** 跨天才变(进系统提示) · **`[现在]`** 每轮实时(不进系统提示)—— **稳定的在前、易变的在后** | ✅ |
383
+ | **T-3** | 精确时间**按需查**(`clock` 技能):它像一块手表 —— **该在你需要时被看一眼,而不是总对着你报时** | ✅ |
384
+ | **T-4** | 时间与状态类判断**一律以本条消息的 `[现在]` 为准**;过期快照不得当现状 | ✅ |
385
+ | **T-5** | **记忆三层加载**:L0 常驻人格 · L1 开场结论 · L2 运行期检索;**不注入全部记忆** | ✅ |
386
+ | **T-6** | **L1 只放"值得常驻的结论"** —— 不放过程、不放当场读数 | ✅ |
387
+ | **T-7** | **每条进上下文的东西都要能报出理由**并留痕:注入了谁 / 为什么是它 / 多少字 / **谁落选了与为什么** | ✅ 回执只写不读 |
388
+ | **T-8** | **主动召回与被动召回分开**:主动看**值不值**(置顶 + 被反复采用),被动看**问得准不准**(查询匹配)—— 判据不同,不共用一套 | ✅ |
389
+ | **T-9** | **索引层是地图,不是仓库**:一簇一行(名称 + 触发线索),整层只占**一个技能**的常驻描述;要细节时按需展开 | ✅ |
390
+
391
+ ### 1.6 新增(找回原文 · 投递留痕 · 结论层)
392
+
393
+ | # | 设定 | 状态 |
394
+ |---|---|---|
395
+ | **N-1** | **`recall` 直通原文四档**:清单 / 按内容找 / 对话目录 / 按区间取原话 —— 逐级下钻,一次一层 | ✅ |
396
+ | **N-2** | **它不替你挑"最重要的那一段"** —— 只给坐标系;选哪一段是器灵自己的判断(它才知道自己为什么要读) | ✅ |
397
+ | **N-3** | 磁盘日志读不到时**回落查库**,并在回执里如实标注那份是**残的**(只含真人消息与有正文的回复) | ✅ |
398
+ | **N-4** | **会话文件名代次**:按 `session(\.v\d+)?\.jsonl\.zstd` 匹配、取版本最高的一代并跳过 0 字节 | ✅ |
399
+ | **N-5** | 检索范围**默认只搜"你自己的会话"**,越界须显式参数;被挡掉的条数**一律报出** | ✅ |
400
+ | **N-6** | **投递留痕**:每一次真正定稿的注入都落一条 —— 注入了谁 / **为什么是它** / 多少字 / **谁落选了以及为什么** | 🚧 只写不读 |
401
+ | **N-7** | **深层库(结论层)**:把过程流水换成**可复用的结论**,分承诺 / 决定 / 事实 / 偏好 / 关系五类;**空结果是正确结果** | 🚧 在做 |
402
+ | **N-8** | **记忆开场改用结论层**:置顶必进 + 被反复采用的排前 + 当轮关键词加成 + **按硬度过滤**(临时条目不进开场)+ **2 席留给最硬的事实** | ✅ |
403
+ | **N-9** | **视角框**:开场先声明人称,并写死「**本会话 / 接下来 / 现在**」指当时那个会话、**不是此刻的指令** | ✅ |
404
+ | **N-10** | 一年半的网页端历史(**1,523 个会话 / 726.7 万字**)已补录进库,`recall` 能直接读到 | ✅ |
405
+ | **N-11** | **条目硬度**:每条结论带 `durable / long / ephemeral` 三档,**临时的那档不进开场** —— 从**根源**判断它是不是临时,而不是靠"同一次会话限几条"去机械地卡 | 🚧 在做 |
406
+ | **N-12** | **被采用才算加固**:条目的"反复加固"由**真的下钻去读它的原话**推进,**不由"被注入"推进** —— 否则会形成"注入越多分越高、分越高越容易被注入"的自我强化 | ✅ |
407
+ | **N-13** | **索引层**已落地:11 簇主题地图(一簇一行)做成**一个技能**常驻,细节按需展开;簇里**不写条数**(条数天天变会毁掉前缀稳定) | ✅ |
408
+
409
+ > ⚠️ **这一节还在补。** 完整规格有**四百余条**,本节先落**对外面**里最稳定的一批(设计理念);其余按主题分批补写 —— 每一条都要从开发期设定**改写成对外语言**并核对状态,这是**写作**而不是复制。
410
+
411
+ ---
412
+
239
413
  ## 致谢(站在谁的肩膀上)
240
414
 
241
415
  设计过程中受这些公开项目与讨论的启发,在此致谢(仅致谢思路,不含其代码):