@nightpainters/dsh-ling 1.6.0 → 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,52 @@
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
+
5
51
  ## 1.6.0
6
52
 
7
53
  **找回原文(能读回一年半前的原话)· 深层库(结论层)· 记忆开场换料**
@@ -56,6 +56,17 @@ export function wireLifecycle(ctx, gate, memory, settings, config) {
56
56
  memory.kvSet('dbg.first_event_sample', '');
57
57
  memory.kvSet('dbg.event_no_session', '');
58
58
  memory.kvSet('dbg.event_stats', '');
59
+ // ⚠️ P5①(蓝队 2026-10-04):上面三家清了,`dbg.diff_stats` 却漏了 —— 于是差分路的读数会
60
+ // **跨重启残留**:新进程还没跑过任何一轮差分,诊断里却躺着一个旧时间戳的"本轮画像",
61
+ // 看起来像"刚跑过、结果是这样"。它现在和另外三家同批清。
62
+ memory.kvSet('dbg.diff_stats', '');
63
+ // ⚠️ P5③(同一处):`evStats` 是**模块级**对象,只在模块加载时归零 —— 同一进程里若二次
64
+ // `wireLifecycle`(换配置重载插件),计数会在上一轮的基数上继续累加,而 kv 里写的是"本轮"。
65
+ // 归零点放到这里,与 kv 的清理**同一时点**,两者才同义。
66
+ evStats.seen = 0; evStats.kept = 0; evStats.dropType = 0; evStats.dropSource = 0;
67
+ evStats.dropEmpty = 0; evStats.dropNoSession = 0; evStats.dropSubagent = 0;
68
+ evStats.types = {}; evStats.lastSid = '';
69
+ evStatsDirty = 0; evStatsAt = 0;
59
70
  memory.kvSet('lifecycle.wired', '1');
60
71
 
61
72
  const inTrackedWorkspace = () => {
@@ -234,8 +234,14 @@ export function dialogTurns(events) {
234
234
  // 子代理种子 387 条 **true→false 回归 0**。
235
235
  let sessionEligible = null;
236
236
  for (const e of evs) {
237
- if (e && typeof e === 'object' && e.type === 'session' && e.header) {
238
- sessionEligible = isMemoryEligibleHeader(e.header);
237
+ if (e && typeof e === 'object' && e.type === 'session') {
238
+ // ⚠️ **踩坑(2026-10-04,实测抓出)**:`session` 事件里**没有 `header` 子对象** ——
239
+ // 判据要的字段(`id` / `parentSession` / `delegationDepth` / `origin`)就**平铺在事件顶层**
240
+ // (`{"type":"session","version":4,"id":"feishu-…","cwd":"…","isSeeded":false,"delegationDepth":0,"agentPreset":"standard"}`)。
241
+ // 第一版写成 `isMemoryEligibleHeader(e.header)` ⇒ 恒为 `undefined` ⇒ `sessionEligible` 永远取不到
242
+ // ⇒ 判据退回 ⑤(只认 rpcId)⇒ **全盘净增 0、F3 一行没生效**,而注释里却写着"已修"。
243
+ // 所以这里传**事件本身**(兼容将来真加了 `header` 的形态)。
244
+ sessionEligible = isMemoryEligibleHeader(e.header ?? e);
239
245
  break;
240
246
  }
241
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nightpainters/dsh-ling",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "器灵 · 跑在你自己电脑上的记忆与人格助手插件:三层记忆、从历史里长出来的人格档案、定型锁与自由生长",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",