@nightpainters/dsh-ling 1.5.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/ACCESS-DESIGN.md +119 -0
- package/CHANGELOG.md +627 -0
- package/DESIGN.md +461 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +236 -0
- package/cordis.patch.yml +16 -0
- package/lib/audit/access-log.js +533 -0
- package/lib/audit/index.js +92 -0
- package/lib/audit/patch-spec.js +195 -0
- package/lib/client.js +5069 -0
- package/lib/host/api.js +2584 -0
- package/lib/host/archive.js +194 -0
- package/lib/host/autotree.js +131 -0
- package/lib/host/backfill.js +267 -0
- package/lib/host/classify.js +10 -0
- package/lib/host/clock-skill.js +141 -0
- package/lib/host/clock.js +132 -0
- package/lib/host/corpus-suggest.js +139 -0
- package/lib/host/deepsummary.js +338 -0
- package/lib/host/dsweb-summary.js +225 -0
- package/lib/host/feedback.js +119 -0
- package/lib/host/freeze.js +144 -0
- package/lib/host/genesis.js +176 -0
- package/lib/host/guard.js +184 -0
- package/lib/host/habit-gen.js +273 -0
- package/lib/host/import-file.js +291 -0
- package/lib/host/import-log.js +129 -0
- package/lib/host/inject-common.js +25 -0
- package/lib/host/inject.js +285 -0
- package/lib/host/l1.js +282 -0
- package/lib/host/lifecycle.js +367 -0
- package/lib/host/memory.js +2389 -0
- package/lib/host/mode.js +101 -0
- package/lib/host/persona.js +214 -0
- package/lib/host/retitle.js +214 -0
- package/lib/host/rules.js +195 -0
- package/lib/host/scan-dsweb.js +111 -0
- package/lib/host/seal.js +17 -0
- package/lib/host/settings-file.js +49 -0
- package/lib/host/snapshot.js +168 -0
- package/lib/host/suggestions.js +47 -0
- package/lib/host/summarizer.js +103 -0
- package/lib/host/tone-advice.js +112 -0
- package/lib/host/tools.js +1067 -0
- package/lib/host/tree-ops.js +330 -0
- package/lib/host/util.js +165 -0
- package/lib/host/zstd.js +63 -0
- package/lib/index.js +263 -0
- package/package.json +69 -0
- package/tools/apply-access-log-patch.mjs +295 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
本项目遵循语义化版本:`MAJOR.MINOR.PATCH`。
|
|
4
|
+
|
|
5
|
+
## 1.5.1
|
|
6
|
+
|
|
7
|
+
**审计必修落地 + 一处平台升级事故 + 会话历史可见性**
|
|
8
|
+
|
|
9
|
+
> 一句话:1.5.0 之后又跑了一轮红蓝对抗(109 条发现),必修**逐条落地并单独实测**;期间揪出一处「平台升级把事件改名,导致记忆与人格注入**整段静默失效**」的事故;另外修好了「接入历史只看得到三成会话」。
|
|
10
|
+
|
|
11
|
+
### 一、平台升级事故:一个改名,让注入整段静默失效
|
|
12
|
+
|
|
13
|
+
DSH `0.1.7` 把事件 `agent/session-start` 改名成 `agent/created`(**载荷形状不变**)。器灵监听的是旧名 ⇒ 在 0.1.7 上**零命中** ⇒ 会话定稿**从不发生** ⇒ 注入的「无快照 + running」分支**只送出时间锚**,记忆正文与**人格全部空白**。
|
|
14
|
+
|
|
15
|
+
- **失败形态纯静默**:无异常、无日志、无告警、库里零痕迹;
|
|
16
|
+
- **修法**:`lifecycle.js` **两个名字都注册**,并加 3 秒短窗口去重(跨平台版本,不赌它保留哪一个);
|
|
17
|
+
- **验收**:`branch_log_wm.last` 392 → 395,注入正文回来;
|
|
18
|
+
- **教训**:平台契约不只是**字段**,还包括**事件名** —— 凡 `ctx.on('<事件名>')`,平台升级后必须 grep 一遍这个名字还在不在。
|
|
19
|
+
|
|
20
|
+
### 二、必修 8 条(1.5.0 审计的必修,全数落地)
|
|
21
|
+
|
|
22
|
+
- **发布链**:游离的 `.bak` 文件会卡死发布前检查 ⇒ 移入归档区;
|
|
23
|
+
- **补丁锚点多 cohort 化**:E4 补丁的锚点在不同 DSH 版本里写法不同 ⇒ 改为**逐 cohort 候选**,**恰好一个唯一命中才动手**;多个同时命中 = 歧义 ⇒ **拒绝改动**,全不命中 = 漂移 ⇒ 退出码 2 等人工更新锚点(宁可不动,也不在核心代码里插错地方);
|
|
24
|
+
- **时间锚出口补中和**:`context()` 出口补 `neutralizeMustache`,堵住一个从新出口复活的注入面问题;
|
|
25
|
+
- **器灵记自己的端点**:不再往核心补第三条补丁,改为在器灵自己的 `guard` 上挂 observe ⇒ **器灵自己拦下的请求也进访问日志**(受控实测:修复前 0 行 → 修复后 4 行);
|
|
26
|
+
- **回灌写入侧跨源污染**(第三次复发)⇒ `restoreEntry` 写入侧统一走 `rawSessionId`;
|
|
27
|
+
- **主脉记忆**:提炼结果采纳时**备份后覆盖**,并落总变动日志(概括应随主脉一起长);
|
|
28
|
+
- **主脉素材排除已遗忘条目**(遗忘条目被提炼灌回库 ⇒ 遗忘前 1 → 遗忘后 0 → 撤销后 1);
|
|
29
|
+
- **「↶ 撤回」修好**:状态原本放在组件体内、被重渲染清空 ⇒ 改存 ref。
|
|
30
|
+
|
|
31
|
+
### 三、会话历史:只看得到三成(本轮新必修)
|
|
32
|
+
|
|
33
|
+
会话文件名是**随平台版本变化的**:`session.jsonl.zstd` / `session.v3.jsonl.zstd` / `session.v4.jsonl.zstd`。器灵原先**写死**了第一种 ⇒ 本机实测只认得 **53** 个会话,**漏掉 124 个**(同一个会话目录里的多代文件只算一个,去重后本机共 **177** 个)。<sub>下面这几个数字是修复当天的**实测快照** —— 会话目录会持续增长,你自己机器上测到的数不会与它相同,判据是「候选数显著变大、解码失败为 0」。</sub>
|
|
34
|
+
|
|
35
|
+
- 改为按 `session(.v\d+)?.jsonl.zstd` 匹配并**取版本号最高**的一代(跳过 0 字节的未完成文件);同一会话目录只出一个候选,**天然去重**;
|
|
36
|
+
- **实测**:候选 **53 → 177**,解码失败 **0** ⇒ 新旧两代会话格式**内容形状完全兼容**,不必做格式适配;
|
|
37
|
+
- 顺带修掉一个真病灶:**「0 候选」与「这台机器没有历史」原先不可区分** —— 正是它让上述缺陷**静默了两周**。现在 0 候选时会显示 `⚠` + 一行具体原因(没找到会话根目录 / 没有会话目录 / 都是空文件 / 文件名代次不符),并列出目录里**实际见到的文件名**。
|
|
38
|
+
|
|
39
|
+
### 四、`rejection` 不再误标
|
|
40
|
+
|
|
41
|
+
访问日志的 `rejection` 原先**从状态码反推**(`403 ⇒ untrusted-host`),前提是「全系统只有核心栅栏会返回 403」。第二节那条修好之后,器灵自己拦下的请求也进日志了 ⇒ **拒因被大面积误标**(7 种拒因里 **6 种**被记成 `untrusted-host`)。
|
|
42
|
+
|
|
43
|
+
现在改为**真因优先**:判定完成后把真实拒因挂在请求上,日志优先用它,读不到才退回旧判据(核心栅栏的 403 依然正确)。取值域因此从 3 类扩到 **9 类**:`no-host` / `bad-host` / `untrusted-host` / `cross-site` / `origin-mismatch` / `no-cookie` / `empty-cookie` / `bad-cookie` / `method-not-allowed`(`bad-cookie` 仍由 401 退路产出,没有消失)。
|
|
44
|
+
⇒ **1.5.0 条目里那句三值清单(`untrusted-host · no-cookie · bad-cookie`)自本版起不再完整** —— 那三个值**都还在产出**(核心栅栏的 403 与 401 两路照旧),只是已不是全部,以本版为准。
|
|
45
|
+
|
|
46
|
+
### 五、设备标识不再落凭据原文
|
|
47
|
+
|
|
48
|
+
访问日志里的 `deviceId` 是**设备凭据的原文**。现在改为 **sha256 不可逆摘要**(取前 16 位十六进制):日志仍能回答「是不是同一台设备」,但**不再持有可用的凭据**。
|
|
49
|
+
|
|
50
|
+
### 六、归档 / 遗忘 / 回灌:从零覆盖到两处 fail-closed
|
|
51
|
+
|
|
52
|
+
> 起因:1.5.0 新加的 `memory_forget` / `memory_backfill` 把「删掉的还能不能捞回来」押在这条通路上,而它此前是**零测试覆盖的裸区** —— 审计里最危险的一块。
|
|
53
|
+
|
|
54
|
+
- 新增 `tests/forget-backfill.test.mjs`(**307 行 / 86 条断言**),四段对账:**行数守恒**(归档里的 `turns.jsonl` 一行不少)· **召回**(遗忘后不再被想起来)· **撤销**(`undo` 之后回来)· **回灌往返 hash**(灌回去的概述与归档里的一致);另加 **import 源单独断言**(裸 id 零出现 —— 跨源污染此前复发过,这是防下一次的回归网);
|
|
55
|
+
- **回灌不再丢标题锁(B-02)**:`upsertOverview` 的 INSERT 列清单与 `ON CONFLICT SET` 里**都没有** `title_locked` / `title_by` ⇒ 硬删之后回灌,「这是主人亲手定的名」这个状态会**丢**(旧 SET 里那句 `title=CASE WHEN title_locked=1 …` 只是**读锁**的写法,救不了已经丢掉的锁)。现在改为「**调用方显式给了才写**」(对象里带 `title_locked` 这个键才算声明);概述器重建与批量改名因此不会再顺手把锁抹掉,解锁只能走 `unlockTitle`;
|
|
56
|
+
- **`forget` 空归档守卫(B-05):两处 fail-closed** —— 归档侧给出 `empty` 判据(0 轮原文 + 无 summary 正文 + 无 sessionMeta),数据层再加一道**验盘闸门**(没有归档路径 ⇒ 拒;未声明轮数 ⇒ **直接数盘上的 `turns.jsonl`**;声明 0 ⇒ 拒)。旧行为下「条目还在、原文已被硬删」会拿到 `ok:true`、**打上一个永久遗忘标记**,而回执还谎称已归档 —— 那条标记永远召不回任何东西,界面上却显示「忘过了」。
|
|
57
|
+
|
|
58
|
+
### 七、会话内工具面:入口那一簇
|
|
59
|
+
|
|
60
|
+
> 1.5.0 把工具面从 3 个扩到 7 个。本版修的是那一簇的**入口** —— 模型照着提示做会掉进 `no-match`,或者回执说的和库里的不是一回事。
|
|
61
|
+
|
|
62
|
+
- **工具回执改印全长 id(D-T01)**:`tree_read` 概览行尾原先印 8 位短 id,而 `branch_edit` 的查找全是**精确相等**、只认全长 id ⇒ 照提示做必然 `no-match`,而 `no-match` 的提示语还在教「用概览行尾那 8 位」(一个自洽不了的闭环)。现在印全长 id;
|
|
63
|
+
- **`link` 校验两端存在性(D-T02)**:这是**悬空边唯一的产生口**,现在挡在写之前,并把**哪一端**找不到念出来(此前只说「不存在」,调用方只能猜)。`unlink` 有意**不**以「两端必须存在」为由拒绝:库里可能本来就躺着旧版造的悬空边,而它是清掉悬空边的通道 ⇒ 照删,并把 `dangling` 如实回传(说清「清掉的是一条悬空边」);边本来就不存在则回 `no-match`,不写假留痕;
|
|
64
|
+
- **`weight` 回执如实(D-T03)**:`setBranchWeight` 会把系数夹到 `[0,2]`,而回执原先用**入参**覆盖 ⇒ 请求 99、库里 2、回执说 99。现在回**库里的值**;请求值与落库值不同时另带 `weightRequested`,让回执能说清「被夹了」;
|
|
65
|
+
- **`impact` 闸门(D-T05)**:非空枝删除的闸门此前**形同虚设** —— `why` 表里没有 `not-empty` 这个键,memory 算好的四个数字(子枝 / 会话归属 / 条目归属 / 连边)在 render 这一步被整个丢掉,模型只看到一句「未执行(not-empty)」。现在把四个数字念出来,并给出更稳的做法(先把里面的东西挪走,或者只做 rename / reparent);
|
|
66
|
+
- **`reparent` 不再静默挂到主干(D-T06)**:缺 `parentId` 原先静默挂主干 ⇒ 现在与「父枝不存在」**分开成两个码**,参数缺失直接报错(要挂回主干请显式传 `trunk`);`create` 的默认仍是主干(新建时没有「你忘了给」这回事,性质不同因而保留);
|
|
67
|
+
- **`source` 参数白名单(B-11 / D-T07)**:`forget` / `backfill` 的来源域只在库里真有的域里选;且**目标必须真的存在**才动手,存在性检查放在归档**之前** —— 给不存在的条目打遗忘标记,只会留下一条**永远召不回**的空标记,界面还显示成「忘过了」。
|
|
68
|
+
|
|
69
|
+
### 八、主脉提炼那条链:并发、悬空候选、回执口径
|
|
70
|
+
|
|
71
|
+
> 起因:`/vein/distill` 是本版新链路的入口,而它在最外面**一把锁都没有**;再往里两处,回执与库里的实际情况会分叉。
|
|
72
|
+
|
|
73
|
+
- **提炼并发锁(E2 / C-06)**:`/vein/distill` 原先**没有任何单实例锁**,连点两次 = **两次模型调用** + 同一条主脉**两条候选**。现在复用本仓既有的 `BATCH_RUN` / `batchEnter` 那一套(与 `/conflicts/judge`、`/tree/autobuild/name` 同一套:模块级标志 + 整批 deadline,到点不再开始下一次尝试),响应形态与 `busy` 文案因此与客户端既有处理一致;抢锁在**读 body 之前**(`batchEnter` 是同步的、同 tick 原子,而 `await readBody` 是本处理器第一个让出点 —— 放到它后面,两个并发请求会先读完 body 再一起冲进模型调用,锁等于没上),`finally` 放锁,并发的第二次回中文 `busy`;
|
|
74
|
+
- **悬空候选不再谎报成功(A-06)**:主脉被删之后再采纳它的悬空候选,原先照旧回 `ok:true` + 一个**已删枝的 id** —— 底层把 `setConvBranch` 的 `{ok:false, reason:'no-branch'}` 吞掉了,归属其实落回了主干。现在**采纳前先做真实存在性门**(候选所属主脉此刻还在不在 branch 表里,不在就**一个字节都不写库**),**写完再复核一次归属**,没落上就如实回「记忆已写入,但没能挂到主脉下」;
|
|
75
|
+
- **锁的语义说清楚(A-12)**:`title_locked=1` 只让 `upsertOverview` 不动 title,**summary 照样被覆盖** ⇒ 主人手改过名字的那条主脉记忆,下次采纳会换掉**正文**、留着名字。这把锁**不该由端点扩权去挡主人**(采纳本就是主人的显式动作,前面还有解锁门),该做的是**把这件事说出来**:回执带 `naming{title, titleBy, locked}` 与一句中文(名字是你手改过的、按约定保持不动),而不是让主人以为正文也被保住了;
|
|
76
|
+
- **失败不再抛英文内部码(E9)**:`/vein/suggestion/resolve` 失败时(`empty-text` / `bad-id` / `archive-failed` …)补一个中文 `message`;`ok` / `reason` **逐字不动**(客户端可能仍在读 reason,兼容优先),底层已带 message 的优先用它。
|
|
77
|
+
|
|
78
|
+
### 九、前端一簇:吞错、骗人的提示、写错内容的卡片
|
|
79
|
+
|
|
80
|
+
- **三处吞错(E6 / E7 / E13)**:断边、「忽略候选」(语料建议与反馈各一处)、器灵概览那几路请求,原先**不看 `r.ok`、也没有 `.catch`** —— 而 `api()` 只在 fetch **reject** 时抛错,后端「HTTP 200/400 + `{ok:false, reason}`」这条**业务失败**路径不会抛 ⇒ 后端明确回了失败,界面照样报「已断开 / 已忽略」。现在统一走一个共用判读器 `apiOk()`(后端若已带中文 `message` 就优先用它,不用 reason 覆盖更准的说明),失败时报中文原因;概览页另修掉一个「永久停在『加载器灵概览…』」的坑,拿不到的那几项**如实说缺哪几项**,不再假装数字是 0;
|
|
81
|
+
- **删掉骗人的 Ctrl 多选(E12)**:记忆中心的提示一直教「Ctrl+单击 = 加选/减选」,而那个染色的选中集在拖放被裁掉之后**再无任何消费者** —— 它从「双真值缺陷」变成了更坏的东西:**一个骗人的提示**。修法是**把提示与实现一起删掉**(`treeSel` 成为唯一选中真值:点击 = 选中本行、再点 = 取消),而不是「让它生效」:这个视图是只读的,没有批量动作可挂,后端也没有被它调用的批量端点。(侧栏树那一路的多选**另有消费者** —— 批量拖动本身就吃那个集合,故原样保留。)
|
|
82
|
+
- **待审核候选只渲 6 条(E3)**:标题如实写「待审核 20 条」,下面只画 6 张卡,没有分页、也没有「还有 N 条」⇒ 第 7 条之后的候选**永不可达**(既看不到,也无从采纳/驳回)。根因是**前端硬截断** `slice(0,6)`(同一次渲染里标题就用 20 算,说明 20 条**已经在手里**);现在**全量渲染**,并把请求的 `limit` 从 20 提到 **100**;
|
|
83
|
+
- **判定循环的忙锁口径(E14)**:三个复盘按钮的兜底原写成「单次操作总时长上限 180s」,而 `judge()` 的最坏形态是 **40 轮 × 每轮一个 HTTP 请求**,单轮最坏 ≈ 210s(后端整批上限 120s + 在飞那一条收尾)⇒ 一到点锁自动解除,主人再点一次就能**并发起第二个判定循环**(后端有单实例锁,不会双倍烧模型,但界面会莫名报错、半途而废)。现在把语义改成「**无进展容忍上限**」(默认 180s;judge 单轮另给 240s 窗口),判定循环**每轮回来续期** —— 「既不 resolve 也不 reject 的挂死」照样解除,「活着、只是在跑很久」不再被误判;
|
|
84
|
+
- **审核卡片的 key(E8)**:下标当 key + 非受控 `defaultValue` ⇒ 列表一缩短(采纳/驳回掉一条,或重拉后顺序变了),React 按**下标**复用同一张卡片的 DOM:第 i 个框里**留着上一条候选的正文**,而卡片元信息与按钮闭包已换成第 i 条 ⇒ 点「采纳」= **把 A 的正文写进 B 的主脉**,静默写错内容、界面没有任何迹象。改用条目主键 `sg.id` 立 key(正文本身也进 key,内容变了 ⇒ 重挂载);**并脉 / 连边的下拉也是常量 key,而「并到」按钮读的正是那个 DOM 值 ⇒ 可能静默并错枝**,一并改成按选中节点立 key。
|
|
85
|
+
|
|
86
|
+
### 十、安装与分发:包名改了
|
|
87
|
+
|
|
88
|
+
> npm 上 `dsh-ling` 这个名字**已被他人占用**,本版起发布名是 **`@nightpainters/dsh-ling`**。
|
|
89
|
+
|
|
90
|
+
- `package.json` 的 `name` 改为 `@nightpainters/dsh-ling`,并加 **`publishConfig.access = "public"`** —— scoped 包**不显式声明会默认 restricted**(别人装不上,还会在 npm 上占住一个私有包名);
|
|
91
|
+
- README 补**两条安装路径**:官方入口 `dsh plugin add`(在 profile 目录里转发给 pnpm,并自动登记进 profile 的 bundles 层列表)与**源码 / 本地开发**路径(`pnpm add file:<路径>/dsh-ling`);
|
|
92
|
+
- ⚠️ **最容易配错、且配错就整片前端黑屏的一处**:客户端 bundle **自报的注册 id 必须逐字等于 npm 包名** —— `lib/client.js` 顶部 `window.__ModuleLoader__.load({ id: … })` 的 id 已随包名改为 `@nightpainters/dsh-ling`;
|
|
93
|
+
- **机制**:客户端模块表以**包名**为 row id(`dsh-client-modules/lib/index.js:884` 的 `graphRow(packageName, …)`),浏览器按 row id 判断"该插件模块是否到位";bundle 自报 id 与之不符时,系统会**回退去取单资源 URL,把同一份字节再执行一遍**,第二次注册撞上去重守卫 ⇒ `duplicate factory registration` ⇒ `web boot: 1 entry did not activate` ⇒ **前端整片黑屏**(本版开发期间实测踩中两次);
|
|
94
|
+
- `cordis.patch.yml` 的 `id: dsh-ling` **保持不动**(它是 Loader 的身份键,与 import 无关),`name` 必须是 npm 包名(它就是被 import 的说明符);
|
|
95
|
+
- **防复发**:`tests/unit.mjs` 已加「注册 id === `package.json` 的 `name`」断言 —— 以后再改名,它会先红;
|
|
96
|
+
- 已在 profile 里按旧包名登记过的:把那一行的 `name` 换成新包名,或直接用 `dsh plugin add @nightpainters/dsh-ling` 重装一次。
|
|
97
|
+
- README 补上**访问日志与核心补丁的总开关**:`accessLog.enabled`(**默认开**)、关掉之后**退化的只有留痕这一件事**(不挂观察钩子 / 不写日志文件 / 健康读数归 `null`,栅栏与插件其余功能不受影响),以及核心补丁 `--revert` 这条退路 —— 此前**有开关、没文档**,看文档的人不知道它能关;
|
|
98
|
+
|
|
99
|
+
### 十一、注入面:段头、分隔与计数(本轮同批)
|
|
100
|
+
|
|
101
|
+
- **段边界与排版**:规矩 / 习惯的段头去掉括号里的**元描述** —— 那是写给主人看的说明,不该进模型上下文(且「可直接追加」读起来像一条授权指令);底线段头由 `底线:` 统一成 `[底线]`,三类段头自此都是**纯标签**;段与段之间补空行(段内条目仍单换行);条目正文里的方括号做**转义**,否则正文能自造一条段边界 —— 规矩 / 习惯 / **底线**三类条目统一转;渲染出口再按同键**各去一次重**(规矩 / 习惯):保留首次出现、顺序不变、只在渲染期生效、绝不回写存储;
|
|
102
|
+
- **改动播报的数字改成真的**:注入面末尾那句「另有 N 笔改动」原先取的是**两个写死上限的差值**(窗口上限减渲染上限),与库里实际剩多少**无关** —— 积压一旦超过读取窗口,它就恒等于同一个数、不再随真实积压变化;现在**直接问库**(一条主键范围扫描的独立计数,代价与积压量无关),报的是**真实剩余条数**。
|
|
103
|
+
|
|
104
|
+
### 十二、发布面文档:形态声明、措辞统一、机制性声明勘误(本轮同批)
|
|
105
|
+
|
|
106
|
+
- **README 新增「本项目是什么形态」一节**(开头项目介绍之后、安装段之前):本地运行的软件、跑在你自己的机器上;上游不提供任何在线服务、不运营任何服务端、不托管任何用户数据(作者不接触、不接收你的会话内容与记忆库);**不上架任何应用商店**,仅通过 GitHub(源码)与 npm(包)分发;模型与 API Key 由使用者自行提供;不提供医疗 / 心理 / 法律或紧急救助服务。同节另附三句:**定性归属**(本项目不定义你与它的关系)、**AI 生成标识**(回答与记忆摘要由人工智能生成,可能存在错误,请自行判断)、**面向性**(面向具备自行配置与运维能力的用户;不面向未成年人,也不提供在线陪伴服务);
|
|
107
|
+
- **定型门的说法统一为「庄重感」**:README / `lib/host/seal.js` / DESIGN 三处原先写作"仪式感 / 仪式"的字样改为**庄重感**,与代码里既有的「锁只造庄重」同一口径 —— **语义零变化,纯措辞**(手术门的定性没变:它是庄重与自我提醒,不是安全边界;真正把门的是来源栅栏);
|
|
108
|
+
- **机制性声明勘误一批**:抽取三份发布面文档里**可证伪的机制性声明**逐条对代码事实核验,**不符 6 条、部分符合 13 条,全部点名旁路并改准**。典型:习惯生成器的阈值口径(代码已收紧为「≥4 次且跨 ≥3 个会话」,文档仍是旧值)· **时间锚示例给了代码产不出的精度**(日期的时分)⇒ 改为只给日期 + 时段,并注明要精确到分钟走 `clock` skill · 规矩 / 习惯的「上限」实为**建议上限**(超出只在写入后提醒,**不拦**)· 「必须带原话作凭据」与「reason 必填」实为**仅器灵工具通道**强制,**面板豁免** · `Ctrl+Z` 实际只覆盖**复盘栏的拖动 / 连边**(记忆中心那一份监听早已删掉) · L0 人格实为**顶层会话与会话分叉**注入、**子代理会话不注入** · 「内容零改写」有一个例外(主脉提炼**再次采纳**会覆盖该主脉记忆的上一版,**覆盖前自动归档**,注入面说明"旧版已归档") · 打分公式里的 `× 枝系数` 删掉(该系数**预留、当前恒按 1 计、不参与打分**)· 导入返回字段名与实现对齐 · 并**删掉一句不存在的承诺**(原先写"登记由单测兜底",实际是**手写清单**,漏登记不会被自动发现);
|
|
109
|
+
- **这一批为什么会存在**:不是手滑,而是**文档写得比代码具体,就会长出偏差** —— 代码一改,文档不会自己跟着变,而读文档的人会把它当事实。三类最易失真:**具体数字 / 机制名 / UI 可见性**(声称"面板可见"的,后端接口里有 ≠ 面板上看得见);
|
|
110
|
+
- **接住这条规律的动作(发布前常设一步)**:把**机制性声明核验**写进发布手册的固定动作 —— 逐条拎出可证伪的声明;数字类去代码里**把那个常量找出来**;机制类**主路径之外必须查旁路**(面板路径 / HTTP 路径 / 工具通道三条都要查);判「符合」必须给代码证据,不接受"听起来合理"。核验基线文件随仓库保留,下次对着它看差异 —— 让这条规律**有动作接着**,而不是靠下次还记得;
|
|
111
|
+
<sub>⚠️ 顺带一句边界:合规检查(关①.6)只拦**禁用措辞**与**声明缺失**,**不检查声明是否正确**;而判据表本身就是从同一批文档里拎出来的,带着同样的理解 ⇒ **它抓不到"文档与代码不符"这一类**,那一类靠上面这条核验动作接。</sub>
|
|
112
|
+
- **随仓库新增判据表** `tools/compliance-spec.mjs`(禁用措辞 / 必须存在的声明 / 出站白名单;纯数据 + 纯函数,不读盘也不写盘),供发布链路复用;**它不入 npm 包,也不参与运行期**。
|
|
113
|
+
|
|
114
|
+
## 1.5.0
|
|
115
|
+
|
|
116
|
+
**记忆树闭环 + 器灵侧正规通道 + 可取证访问日志**
|
|
117
|
+
|
|
118
|
+
> 一句话:记忆树从「看得见」走到「器灵能亲手改」;时间从「每分钟对着人嚷嚷」改成「按需查」;访问日志从「一行都没有」变成「可取证」。
|
|
119
|
+
|
|
120
|
+
### 一、主脉提炼(R7 · D9-b 第 4 项)
|
|
121
|
+
|
|
122
|
+
主脉不只是一层目录 —— 它可以有自己的记忆。
|
|
123
|
+
|
|
124
|
+
- 新增 `POST /vein/distill`:取该主脉**及其子枝**的概述,交给模型提炼共性;四道守卫分别回应「主脉不存在 / 不是主脉 / 没素材 / 那类失败」;
|
|
125
|
+
- **提炼结果不直接进记忆**,先进 `vein_suggestions` 候选表(schema **v11**),主人**可改后采纳**;驳回只改状态;
|
|
126
|
+
- 采纳写入 `conv_overview(source='vein', origin='vein-distill')` 并挂到该主脉自己名下 —— 天然享受标题/锁/权重/深摘;`sources` 记录派生来源(哪几条枝,截 40 条);
|
|
127
|
+
- 提炼用**器灵自己的全局模型**(不是小助手):「你在给自己做整理,别让小助手代劳」—— 判断类工作归器灵,压缩/分类类留给小助手;
|
|
128
|
+
- 侧栏新增「✦ 提炼主脉」(选中主脉时出现;选中**枝**时显示它所属的主脉并直接对那条提炼)与审核卡片;
|
|
129
|
+
- **采纳需要解锁**(进入复盘模式)—— 它写记忆、改归属,与改名/并脉同级;**提炼不锁**(只产草稿)。
|
|
130
|
+
|
|
131
|
+
### 二、器灵侧正规通道(Part G · G1)
|
|
132
|
+
|
|
133
|
+
> 起因:复盘时器灵要改记忆树却**没有任何 agent 侧工具** —— 只能伪造成本机进程走 UI 的 HTTP 口、或者直接开库;留痕还记在主人名下。
|
|
134
|
+
|
|
135
|
+
- 新增 4 个会话内工具:**`tree_read`**(读树 / 读某枝会话 / 看待理矛盾)· **`branch_edit`**(建枝 / 改名 / 挂载 / 连边 / 断边 / 调权重 / 删枝)· **`memory_forget`**(软遗忘)· **`memory_backfill`**(回灌);工具面 3 → 7;
|
|
136
|
+
- 把树操作从 HTTP handler 抽成 `lib/host/tree-ops.js`,13 个 handler 变薄壳 —— **界面与工具共用同一份实现、同一份留痕**,零行为变更;
|
|
137
|
+
- `branch_log` 新增 **`actor`** 列(schema **v12**):器灵改的写「器灵」,主人改的写「主人」;
|
|
138
|
+
- **永不彻底删除**:`memory_forget` 先把原文与概述归档到 `forgotten/<时间戳>-<原因>/`(`turns.jsonl` 逐行 JSON、可 grep,`README.md` 写改动日志与还原方法),**再**在库里打标记 —— 两条独立还原路径(撤销标记 / 按 jsonl 重建);
|
|
139
|
+
- `memory_backfill` 按「比遗忘更危险」处理:回灌前先归档当前状态,写回的概述带 `origin=backfill-v1` 标记,**绝不夺走用户态**(importance / hit_count 原样保留);
|
|
140
|
+
- 修复**工具注册不生效**:原先只注册在全局层,而 Web 端按 agent preset 用 `restrict({allow})` 组工具 ⇒ 全局工具被静默过滤(连更早的 `rule_add` 也从未出现)。现在每个 agent 创建时在**它自己的 scope** 补注册一份。
|
|
141
|
+
|
|
142
|
+
### 三、时间锚:从「每分钟嚷嚷」到「按需查」
|
|
143
|
+
|
|
144
|
+
> 「这就像一块手表,它该在人需要的时候被看一眼,而不是总对着人嚷嚷现在几点。」—— 用户
|
|
145
|
+
|
|
146
|
+
- **`[现在] … HH:MM` 从 system 前缀移出**:system 只留 `[今天] 2026-09-23 周三`(**跨天才变**),`[现在] 时段 · 距上次对话:档位` 改挂 `systemPrompt.context()`(会话中、末条消息,每轮重算);
|
|
147
|
+
- 实测收益:**system 侧时间锚逐轮变化 1440 次/天 → 0 次/天**,不再每请求作废整段前缀(连同 13.8k 的工具 schema);
|
|
148
|
+
- `formatGap` 保留原精度,新增 **`formatGapCoarse`**(按**日历天**分档:`刚刚 / 今天早些时候 / 昨天 / N 天前`)—— 纯小时档会把「隔 36 小时」错报成「昨天」;
|
|
149
|
+
- 顺带修掉一个真病灶:原先时间锚可能来自**更早某次注入的快照**(实测:深夜报「凌晨 1:10」而实际 `00:47`);现在是每轮重算;
|
|
150
|
+
- 新增 **`clock` skill**(嵌入式,随插件注册):要精确到分钟的时间时主动查,平时不打扰。
|
|
151
|
+
|
|
152
|
+
### 四、复盘界面与交互
|
|
153
|
+
|
|
154
|
+
- **树多开 + `Shift+C`**:侧栏枝展开由「单开」改「多开」(各节点独立),`Shift+C` 一键折叠全部(输入框内不触发;只清展开态、保留成员缓存);
|
|
155
|
+
- **主脉认得出来**:侧栏列表行首 `🌱 主干 / ◆ 主脉 / · 枝`;
|
|
156
|
+
- **连边不再铺满**:有连边的节点显示 `⇄N` 徽章,选中该行才展开「连到哪里」;
|
|
157
|
+
- **记忆中心选中与侧栏对齐**:直接点击 = 只选这一条(`Ctrl+单击` = 加选/减选);
|
|
158
|
+
- **工具条左对齐**(就地覆盖,不动全局 `.btnrow` —— 它被另外 15 处按钮行复用);
|
|
159
|
+
- 主脉提炼结果紧跟「✦ 提炼主脉」入口;「最近改动」移到「树的保存 / 恢复」之下;
|
|
160
|
+
- **保存快照不再弹「有 N 笔改动还没跟器灵说过」**:保存不改树 ⇒ 留痕照写但**不计债**(`action='snapshot'` 排除);恢复 / 改归属 / 改名等真改动照旧计入。
|
|
161
|
+
|
|
162
|
+
### 五、修复
|
|
163
|
+
|
|
164
|
+
- **前端黑屏(React #310)**:新增的 `Shift+C` effect 落在 `LingReviewBody` 的 early return **之后** ⇒ 首次渲染 26 个 hook、数据到达后 27 个 ⇒ React 卸载整块面板。hooks 已上移到所有 early return 之前,并在源码内写死位置纪律;
|
|
165
|
+
- **主脉提炼「模型调用失败」**:换通路时只改了取值、没改判据(`llmOnce` 返回 `{text}`,没有 `ok` 字段)⇒ 每次提炼都走失败分支(而模型其实答了)。判据已随返回形态整体走,并加**三档重试**(`off/1200` → `low/2400` → `off/2400` + 裁短材料),失败回执带 `attempts` 便于诊断;
|
|
166
|
+
- **采纳后记忆「不见了」**:`resolveVeinSuggestion` 只写概述、没写归属 ⇒ 解析链落到**主干**;`MEM_SOURCES` 与客户端来源下拉也缺 `vein`(那条记忆当时**无法置顶/删除**、也没有筛选入口)。三处一次改齐,存量已回填;
|
|
167
|
+
- **`tree_read` / `branch_edit` 八条缺陷**(第三方实测报告 7 条 + 复核发现的第 8 条):`lossless()` 兜底(返回值带 `undefined` 会被平台的无损 JSON 校验判成整次失败,而**写已提交** —— 调用方重发即重复写)· 成员计数取值 · `no-match` 与空枝分离 · `limit` 生效 · 行尾短 id · `links` 回传 · `kind` 按 action 分流 · **create 回执丢失新枝 id**(「建枝 → 立刻改它」链路断裂)。
|
|
168
|
+
|
|
169
|
+
### 六、E4 · 可取证访问日志(`lib/audit/`)
|
|
170
|
+
|
|
171
|
+
起因:2026-09-24 19:49:51 进程被 `dsh-setting-restart` 调度重启,8000+ 文件的全机扫描也答不出「是谁调用的」—— 因为**整个系统没有一行访问日志**。
|
|
172
|
+
|
|
173
|
+
- 新增 `lib/audit/access-log.js`:JSONL 落盘 `$DSH_HOME/logs/access-YYYY-MM-DD.jsonl`(按天切 · 总量上限删最旧 · 当天永不删),让「谁 · 何时 · 从哪台设备 · 调用了哪个端点 · 结果如何」事后可回答;
|
|
174
|
+
- 字段:`at`(本地时间+毫秒+偏移)/`method`/`path`/`status`/`rejection`(`untrusted-host` · `no-cookie` · `bad-cookie` —— 本版已扩至 9 类,见 1.5.1 节)/`ip`/`fwd`/`host`/`ua`/`deviceCookie`/`deviceId`/`bytes`/`ms`;高危端点(`settingRestart/restart`、`/dsh-ling/*`)额外打 `risk`,`/api/remote.mux` 升级请求打 `phase:"upgrade"`;
|
|
175
|
+
- **只记「发生了什么」,不记「内容是什么」**:请求体 / 响应体 / cookie 值一律不落,查询串一律丢弃;
|
|
176
|
+
- **覆盖边界**:只记 `/api` 与 `/api/remote.mux` 的 upgrade —— 静态资源与非 `/api` 路径不记;
|
|
177
|
+
- `tools/apply-access-log-patch.mjs`:给宿主 core 打两行**可选调用**(`/api` 唯一入口 + `/api/remote.mux` upgrade),支持 `--check` / `--revert` / 幂等重放;插件缺席或补丁未打时那两行是 no-op,**宿主照常启动**;
|
|
178
|
+
- 同步落盘(`appendFileSync`):异步缓冲会丢掉「调度重启后进程退出」前的最后一行,而那一行恰恰最重要;
|
|
179
|
+
- `tests/access-log.test.mjs`(已挂进 `npm test`):四态可辨、字段形状、查询串丢弃、超限裁剪、坏目录静默降级、写入后元数据当场可见。
|
|
180
|
+
- `deviceId` 取值的**权威形态已核实**(远端 UI 的 `pairing.ts` 用 `randomToken()` 生成 32 位十六进制明文写进配对 cookie),单测用**合成值**钉住该形态 —— 不拿真机 deviceId 当测试夹具(那是凭据)。
|
|
181
|
+
- **日志会自我标注良性噪声**:命中自家**部署姿态自检**签名的行多打 `probe:"posture"`(`POST /api/session.list` + loopback + 无 UA + 被 403 拦下,五要素齐全才标注)。该探针由远端 UI 插件每次启动后自带、`403` 正是它的健康结果 —— 标注**不代表放行**,该行 `status` 仍是 403(任务书 §7.5)。
|
|
182
|
+
- **日志标注调用通道**:没有 `user-agent` 头的行多带 `channel:"no-ua"`。它只陈述"**非浏览器直连**"这一事实 —— 这一类**混装两种调用者**(进程内客户端 / 经远端通道代理进来的设备,代理按设计不转发 UA),故**不指认是哪一台**;按端点语义判读的口径见任务书 §7.5。
|
|
183
|
+
- **观测边界(2026-09-25 真实流量见证)**:远端桌面通道的内侧代理按设计只重建 `host` / `content-type` / `content-length` / `accept` 四个头 + 它自己的鉴权 cookie,**不转发 UA、丢弃 Origin/cookie/Sec-Fetch** ⇒ 凡经代理进来的远端设备(手机/平板/物理机)在内侧日志里一律是 `ip:127.0.0.1` + `ua:null` + `channel:"no-ua"` + 授权通过:**能看出"有远端在用",看不出"是哪一台"**。要按设备归因,需在代理侧透传一个**由代理自己写入**的标识头(内部任务书 §7.6)。
|
|
184
|
+
|
|
185
|
+
> 注意:core 那两行补丁位于 `node_modules` 内,**DSH 升级后需重跑补丁脚本**;补丁缺失时插件启动会 `console.warn`。
|
|
186
|
+
|
|
187
|
+
## 1.4.1
|
|
188
|
+
|
|
189
|
+
**审计修复轮 + 复盘守门:改动必须让器灵看到**
|
|
190
|
+
|
|
191
|
+
### 一、红蓝对抗审计的中危项(27 条)
|
|
192
|
+
|
|
193
|
+
1.4.0 发布前修掉了 8 条高危,随后对全量代码做了一轮红蓝对抗(数据层 / 端点与注入面 / 前端,另加一路迁移安全验证),中危及以上本轮全部关闭:
|
|
194
|
+
|
|
195
|
+
**数据层**
|
|
196
|
+
- `branchMembers` 的 `limit` 改为**两路查询共用总预算**(原先各吃一份,展开主干时条数是配额的 2 倍);
|
|
197
|
+
- 新增 `branchMemberCounts()`,与展开**共用同一份谓词** —— 界面上的「N 个会话」不再和展开条数对不上;
|
|
198
|
+
- `resolveConflict` / `reparentBranch` / `setConvBranch` 补**存在性检查**,对不存在的枝不再假成功;
|
|
199
|
+
- `tree_snapshot` 新增 `kind('manual'|'auto')` 列,软上限裁剪**只裁非 auto 的一条**,并拒绝删除最后一份自动快照;
|
|
200
|
+
- `weight_scale` / `visibility` 明确标注为**预留字段、当前不参与打分**。
|
|
201
|
+
|
|
202
|
+
**端点层**
|
|
203
|
+
- 写 0 行时返回 `{ok:false, reason:'no-match'}` 且**不写假留痕**;
|
|
204
|
+
- `judge` / `name` 加**模块级单实例锁与整批 deadline**(120s / 300s),重入直接回 `busy`;
|
|
205
|
+
- `apply` 校验 `parentId`(跳过并计数)与 `bucketId`(去重);
|
|
206
|
+
- `assign` 两侧都做存在性检查(会话侧走 `sessionBranchMap()`,因为 `sessionMeta()` 对未知会话会返回伪造的默认对象)。
|
|
207
|
+
|
|
208
|
+
**前端**
|
|
209
|
+
- 拖动源改用 ref 在 `onDragStart` 统一记录 —— 修好「撤回 0 项」;
|
|
210
|
+
- 拖到会话行解析为**其所属枝**;失败不再假报成功;
|
|
211
|
+
- 钩子清理链式追加(`__lingCleanup`);成员加载失败可重试;只对成功项记账;
|
|
212
|
+
- 删除约 140 行不可达死代码与两套并存的选中真值。
|
|
213
|
+
|
|
214
|
+
### 二、注入感知水位改为按会话
|
|
215
|
+
|
|
216
|
+
原先水位是**全局键**,后果有二:会话 A 消费过的改动、会话 B 再也看不到;且 `invalidateSession` / `refreshIdleSnapshot` 这类「只重建、没开口」的路径也会消费水位。
|
|
217
|
+
|
|
218
|
+
改为**渲染与投递分离**:
|
|
219
|
+
|
|
220
|
+
- `snapshot.js` 只读 `branch_log_wm.<sessionId>`、**不再写入**,并把本段渲染到的 `logMaxId` 带出;
|
|
221
|
+
- `inject.js` 新增 `advanceLogWatermark()`,**只在文本真正要进模型时**推进(四条投递路径),单调不回退。
|
|
222
|
+
|
|
223
|
+
语义取舍:**宁可重复,不可丢失** —— 只重建的快照若已含改动段,下次重建可能重复呈现,但绝不静默吞掉主人的操作。
|
|
224
|
+
|
|
225
|
+
### 三、复盘守门:改动必须让器灵看到
|
|
226
|
+
|
|
227
|
+
> 「互相复盘不应该被我绕过,动记忆树不能是随意的,应该是慎重严肃的。」—— 用户 2026-09-22
|
|
228
|
+
|
|
229
|
+
- 侧栏显示**未同步笔数**(金色警示块):自器灵**上次开口**以来累积了多少笔结构改动;
|
|
230
|
+
- 未同步时**硬拦「退出复盘模式」**,提示「在左边说一句话让它看到」;
|
|
231
|
+
- 判据链:`branch_log_wm.last`(投递时写入)→ `GET /branch-log/pending` → 侧栏;
|
|
232
|
+
- **升级后首次运行不再误报**:该键不存在时以当前最大留痕 id 作起点,机制上线前的历史改动不计入「未交代」。
|
|
233
|
+
|
|
234
|
+
### 四、留痕显示修正
|
|
235
|
+
|
|
236
|
+
`nm()` 原先对**一切查不到名字的 id** 都回退成「(已删枝 …)」,而 `branch_log` 的 `before`/`after` 还会装**快照号**(`#9`)与**记忆条目 id**(`026ebd86`)。现按 id 形态分流(真枝名 / `快照 #N` / 已删枝 / 条目),并补齐 `snapshot` / `restore` / `assign` / `delete` 四个 action 的中文措辞。
|
|
237
|
+
|
|
238
|
+
### 五、测试
|
|
239
|
+
|
|
240
|
+
仍为 **29 套**;本轮新增 10 条断言,其中 `kvWrites.length === 0` 是「渲染期不写任何 kv」的判决性断言。
|
|
241
|
+
|
|
242
|
+
## 1.4.0
|
|
243
|
+
|
|
244
|
+
**记忆树:平铺的记忆库长出了结构 —— 分枝 · 归并 · 血缘 · 复盘**
|
|
245
|
+
|
|
246
|
+
### 一、为什么要有树
|
|
247
|
+
|
|
248
|
+
旧版记忆库是一张**平铺的表**(以 `(source, conv_id)` 为主键),后果有三:
|
|
249
|
+
|
|
250
|
+
- 召回**没有"离我多近"的概念** —— L1 是全库裸排序,分不出"这轮做的工作"与"三个月前的闲聊";
|
|
251
|
+
- **分叉会话是记忆真空** —— 平台 fork 出来的会话既不写入、也不注入(两道闸都按 `parentSession` 直接丢弃);
|
|
252
|
+
- 无法表达**"这几条属于同一段工作"** —— 没有跨会话的工作线概念,只能靠 `source` 粗分。
|
|
253
|
+
|
|
254
|
+
1.4.0 引入**记忆树**。关键认知:**树的节点不是知识分类,是"工作线"** —— 同一段对话既有知识属性(讨论了什么)也有经历属性(什么时候、和谁、为了什么),树按**经历**组织,知识通过主题名与连边表达。
|
|
255
|
+
|
|
256
|
+
### 二、数据模型(schema v5 → v9)
|
|
257
|
+
|
|
258
|
+
| 表 / 列 | 作用 |
|
|
259
|
+
|---|---|
|
|
260
|
+
| `branch` | 枝 / 主脉 / 主干(`id` · `name` · `kind` · `parent_id` · `fork_at` · `weight_scale` · `visibility` · `status`) |
|
|
261
|
+
| `session_meta.branch_id` | DSH 会话 → 枝 |
|
|
262
|
+
| `conv_branch` | **非会话源的枝归属覆盖层**(历史网页端 / 导入条目) |
|
|
263
|
+
| `vein_link` | 枝与枝之间的横向连边(带类型与理由) |
|
|
264
|
+
| `memory_conflict` | 矛盾标记(不改写内容,只记关系) |
|
|
265
|
+
| `branch_log` | 结构操作**留痕**(复盘可审计:改了什么、为什么) |
|
|
266
|
+
| `tree_snapshot` | 树的保存 / 恢复 |
|
|
267
|
+
|
|
268
|
+
**归属解析**两条链并存:DSH 会话走 `session_meta`,历史条目走 `conv_branch` 覆盖层;优先级 `conv_branch` > `session_meta` > 主干。**记忆行不冗余存 branch**,靠 `会话 → 枝` 推导,枝归属变化时无需回写海量记忆行。
|
|
269
|
+
|
|
270
|
+
### 三、血缘加权:枝读祖先
|
|
271
|
+
|
|
272
|
+
检索时按"当前枝 → 目标枝"的**相对位置**取档:
|
|
273
|
+
|
|
274
|
+
| 当前枝 → 目标枝 | 档位 | 权重 |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| 同枝 | `same` | **1.0** |
|
|
277
|
+
| 目标枝是祖先(含主干) | `ancestor` | **0.7** |
|
|
278
|
+
| 旁系 / 祖先读后代 | `side` | **0.4** |
|
|
279
|
+
|
|
280
|
+
`score = 基础分 × 血缘系数 × 矛盾系数 × 枝系数`
|
|
281
|
+
|
|
282
|
+
**刻意不连乘**:血缘系数是**单值**,不沿路径累乘 —— 否则 `0.7^n` 会随深度指数失真(五层后 `0.4^5 = 0.010`,枝等于从记忆里消失)。深树的 min-max 路径搜索留作后续。
|
|
283
|
+
|
|
284
|
+
### 四、一键生成树:机械分段 + 器灵命名
|
|
285
|
+
|
|
286
|
+
主干太粗(1593 条)时,用**两阶段**长出结构:
|
|
287
|
+
|
|
288
|
+
1. **机械分段**(零模型调用):按源分组、组内按时序,相邻间隔超 `gapDays` 或单簇超 `maxSize` 即切 —— 分段是机械活,确定、可复现;
|
|
289
|
+
2. **器灵批量命名**:每批若干簇调本机小助手起名,分批推进、可中断;
|
|
290
|
+
3. **主人审核**:逐簇可改名字,确认后应用建枝。
|
|
291
|
+
|
|
292
|
+
名字跟随记忆标题的处置 —— **器灵起 / 主人可改**。名字含「零散」的簇自动归并到同一主脉「零散会话」下(承认它不是工作线,但**保留而非丢弃**)。
|
|
293
|
+
|
|
294
|
+
### 五、复盘模式:右栏操作台
|
|
295
|
+
|
|
296
|
+
**"进入复盘模式"不是一个开关,是"左会话 + 右复盘栏"这个状态。**
|
|
297
|
+
|
|
298
|
+
- **记忆中心《记忆树》始终只读**(看结构、看连边、看矛盾,不做修改);
|
|
299
|
+
- **右侧栏「器灵 · 复盘」是操作台**:选中节点改名 / 并脉 / 调权重 / 连边;扫疑似重复;模型判定;归并零散枝;快照保存与恢复;
|
|
300
|
+
- **操作方式**:拖动 = 改归属(move),`Alt` + 拖动 = 只连边(link),`Ctrl` + 点击 = 多选可批量拖,`Ctrl+Z` = 撤回一步;
|
|
301
|
+
- **拖动有预提示**:悬停目标出现青色虚线框,与琥珀色选中框区分;
|
|
302
|
+
- **树的保存 / 恢复**:快照只存**结构**(枝 / 归属 / 连边),不存记忆内容;恢复前**自动存一份当前状态**,故"恢复"本身也可撤。
|
|
303
|
+
|
|
304
|
+
### 六、注入感知:器灵看得见主人改了什么
|
|
305
|
+
|
|
306
|
+
人工改树后,`branchLogDigest()` 把**器灵还没看过**的结构改动(水位按自增 id,不重复注入)格式化成中文短句,由 `snapshot.js` 附在注入面末尾:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
【主人刚改动了记忆树】
|
|
310
|
+
- 把「零散记录」从「主干」下挪到了「零散会话」下
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**为什么不做推送**:客户端插件无法往会话注入消息;而复盘对话密集、每说一句就注入一次 ⇒ **感知延迟只有一轮**,体感即实时。器灵不打断主人,只是不漏看。
|
|
314
|
+
|
|
315
|
+
### 七、其他修复
|
|
316
|
+
|
|
317
|
+
- **分叉会话不再是记忆真空**:两道闸由"按 `parentSession` 一律丢弃"改为"**写入所属枝 + 注入血缘链**";seed(继承前缀)由水位 + `isOwnSeq` 双重拦截,父会话历史不会灌进枝;
|
|
318
|
+
- **归档会话不再被召回**:`selectL1` 与矛盾列表均排除 `archived=1` 的会话(此前仅有界面标记,召回路径**从未过滤**);
|
|
319
|
+
- **会话级条目标题三级回退**:概述标题 → `dsh_turns_raw` 首条用户消息(压缩为单行、≤40 字)→ 空;**空壳会话不再显示**(既无原文也无概述的会话在数据层过滤掉,而不是在前端显示短 id);
|
|
320
|
+
- **`{{…}}` 注入面中和**(1.3.1 起)对齐到新的注入段:所有进系统提示的文本统一走 `neutralizeMustache()`。
|
|
321
|
+
|
|
322
|
+
### 八、测试
|
|
323
|
+
|
|
324
|
+
新增 3 套:`tests/branch.test.mjs`(分枝与血缘)、`tests/tree.test.mjs`(主脉 / 并脉防环 / 连边 / 矛盾标记 / 覆盖层)、`tests/autotree.test.mjs`(分段边界 / 命名解析 / 覆盖层只读性),测试套件 26 → **29** 套,全绿。
|
|
325
|
+
|
|
326
|
+
### 九、发布前对抗审计
|
|
327
|
+
|
|
328
|
+
1.4.0 发布前跑了一轮红蓝对抗审计(三路红队 + 迁移安全实测 + 蓝队复核),发现并在发布前修掉:
|
|
329
|
+
|
|
330
|
+
- **写端点缺少方法校验** —— 宿主路由契约里没有 method 字段,插件守卫也不读它,于是一个 `<img src="…/branches/gather">` 就能触发写操作。已集中声明写端点白名单,非 POST 一律回 `405 method-not-allowed`(列在鉴权之前);
|
|
331
|
+
- **`restoreTree` 会静默删枝** —— 恢复一份快照会删掉"快照之后新建的枝",而会话仍指向它们,留下**看不见、数得到、权重还满格**的悬空引用。已改为**只把多出来的枝 reparent 到主干**,并在返回值里回报 `detachedToTrunk` 计数;
|
|
332
|
+
- **注入面泄漏内部 id 与换行** —— `branchLogDigest` 的两个分支漏做名字映射(会把 `vein:<uuid>` 原样送进系统提示),且未压平换行(可伪造独立行)。已补齐映射、压平并限长,写入侧(`logBranch`)同步净化;
|
|
333
|
+
- **前端四处字段错读** —— 把数组当计数(渲染成 `[object Object]`)、不校验 `ok` 就宣告成功等;
|
|
334
|
+
- **补上删枝能力** —— 非空枝默认拒绝并回报影响面(`children` / `sessions` / `convs` / `links`),`force` 时归属回退主干,不留悬空。
|
|
335
|
+
|
|
336
|
+
**迁移安全性实测**:取 9 月 14 日的老库(7 张表)用当前版本打开,直接升到 v9 —— 四类数据(概述 / 原文 / 会话 / KV)**零丢失**,六张新表与三个新列全部就位,`trunk` 自动建好。
|
|
337
|
+
|
|
338
|
+
## 1.3.1
|
|
339
|
+
|
|
340
|
+
**注入面加固:记忆里的 `{{…}}` 不再能让整个会话失效**
|
|
341
|
+
|
|
342
|
+
### 一、这颗雷是怎么来的
|
|
343
|
+
|
|
344
|
+
系统提示模板用 `{{name}}` 占位符做变量插值。宿主的插值实现遇到**未注册的变量名**会直接抛异常,而这一步在 agent 的**每一步**里执行、**外层没有兜底** —— 于是一段文本里只要出现 `{{time}}` 这样的字样,该会话的每一次模型调用都会失败。
|
|
345
|
+
|
|
346
|
+
而记忆库里装的是**真实对话原文**:导入的历史、网页端抓取的问答,里面完全可能出现 `{{pool}}`、`{{rejected}}`、`{{time}}` 这类写法(模板片段、代码、文档正文)。旧版本把这些文本原样送进系统提示,等于**把"数据"当成"模板"展开** —— 一次概括器搬运、一次深摘要,就足以让一段记忆把某个会话彻底锁死。
|
|
347
|
+
|
|
348
|
+
### 二、修复:出口中和(两处)
|
|
349
|
+
|
|
350
|
+
新增 `neutralizeMustache()`:把相邻的两个 `{` 拆开(插入零宽空格),文本对人和模型**保持原样可读**,且函数幂等。
|
|
351
|
+
|
|
352
|
+
```js
|
|
353
|
+
text.replace(/\{(?=\{)/g, '{\u200b')
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- 用 **lookahead** 而非 `.replace(/\{\{/g, …)` —— 后者在 `{{{` 上会漏(替换掉前两个 `{` 后,余下的字符又与原第三个 `{` 相邻);
|
|
357
|
+
- 中和位点两处、覆盖全部注入文本:① **快照成文侧**(`buildSnapshotText` 的 `parts.join`),② **注入最终侧**(`withAnchor`,正文与时间锚各自中和);源码扫描确认返回注入文本的 5 处路径**全部经 `withAnchor`、无旁路**;
|
|
358
|
+
- 无 `{{` 时直接返回原值,零开销。
|
|
359
|
+
|
|
360
|
+
### 三、验证:用宿主真实的渲染函数做对照
|
|
361
|
+
|
|
362
|
+
| 组 | 载荷 | 结果 |
|
|
363
|
+
|---|---|---|
|
|
364
|
+
| **A 未中和** | 未注册名 / 三连占位 / 畸形 `{{}}` / 真实对话里的 `{{pool}}` | **4/6 抛异常** |
|
|
365
|
+
| | 已注册名(`{{model}}`)、无闭合的裸 `{{` | 通过 —— 前者被**静默替换**成变量值、后者按字面保留,两者仍属有害 |
|
|
366
|
+
| **B 同批中和后** | 6/6 | 全部通过,且逐字可还原 |
|
|
367
|
+
| **C 真实链路** | 快照 → 宿主渲染 | 通过,快照内无紧邻 `{{` |
|
|
368
|
+
| **D 对照** | 把 C 的零宽字符还原后**再送渲染** | **立刻抛** `unknown prompt variable "{{time}}"` |
|
|
369
|
+
|
|
370
|
+
D 组是关键:只证明"中和后不炸"无法排除"这份载荷本来就不致命"。
|
|
371
|
+
|
|
372
|
+
发布前另做一次**受控爆破**:把一条真实记忆的标题临时改成 `{{time}}{{date}} 排雷验证`(该记录 `hit_count 57`,必然入选注入面)→ 开新会话说话 → **正常回复**;进程外复现同一会话的快照,长度与 live **完全一致(1858 字符)**、零宽字符 2 个、未中和 `{{` **0 个**;验证后靶记录逐字段还原。
|
|
373
|
+
|
|
374
|
+
### 四、测试
|
|
375
|
+
|
|
376
|
+
新增 `tests/mustache.test.mjs`(中和本体 / 两个出口走真实调用 / 源码护栏,含"未中和确实命中宿主正则"的反证),测试套件 25 → **26** 套,全绿。
|
|
377
|
+
|
|
378
|
+
## 1.3.0
|
|
379
|
+
|
|
380
|
+
**记忆标题系统(D2)· 记忆中心可见性(D8)· 导入诚实化(S5)· 小助手地址可配置(S7)· 概述器不再硬截首句**
|
|
381
|
+
|
|
382
|
+
### 一、标题是记忆的"名字"(D2)
|
|
383
|
+
|
|
384
|
+
旧行为把首条用户消息**硬截 46 字**,于是 60 条 dsh 概述里 **26 条是半句话**:
|
|
385
|
+
|
|
386
|
+
- 「器灵,接下来在这里继续开发生长型人格与记忆树模块,继续走你的新生之路。 我刚刚对1.2.1的」
|
|
387
|
+
- 「刚才我为本虚拟机安装解压压缩软件<360压缩>,由于操作过快,被捆绑了360本体,随后我在物」
|
|
388
|
+
|
|
389
|
+
三层改造:
|
|
390
|
+
|
|
391
|
+
1. **启发式**(零成本、离线可用):剥掉「器灵,」「晚上好器灵,」这类称呼/问候前缀;上限 46 → 58 并在**句读处收尾**而不是硬切;首句是「你好」「下午好」「已测试对话」这类无信息量句时改取后续有实义的首句;结果若含路径/URL/系统文本则一律弃用(宁可不动,也不产出更糟的名字);
|
|
392
|
+
2. **小助手起名**:`POST /memories/retitle` 用本机小助手读会话原文/摘要生成一句真标题 —— 实测「器灵,接下来在这里继续开发生长型人格与记忆树模块…」→「**修复审计漏洞并优化记忆库关联查询机制**」;
|
|
393
|
+
3. **名字一旦定过就不可覆盖**:`conv_overview` 新增 `title_locked`(schema v3)与 `title_by`(v4)。`POST /memories/rename`(主人手改)与 `POST /memories/retitle`(机器起名)**都上锁** —— 概述器每轮重建、批量重命名都不再覆盖它;要交还机器可解锁。记忆中心卡片新增「改名」「AI 起名」按钮与标记:**三条定名路径(主人手改 / 机器自动 / 来历未记录)统一显示「🔒 已定名」**,定名者只藏在悬浮说明里(「你起的名字…」/「自动起的名字…」)—— 不制造等级感;也不用「暂定」二字(人对一段记忆的概括本就是暂定的、会随阅历改变,但界面要让人确认"它已经落了笔")。用词不写死"小助手"(起名可能走本机小助手,也可能回落当前全局大模型)。悬浮说明点明:**这把锁锁住的是"自动重写",不是主人:手动随时可以再改**。
|
|
394
|
+
4. **引擎就近选择(auto)**:`POST /memories/retitle` 默认 `engine:'auto'` —— 优先本机小助手(零成本),探测不到才落到**当前全局大模型**,并把实际用的引擎与原因写回响应(`engine` / `note`)。不静默换引擎:用了哪个看得见。
|
|
395
|
+
|
|
396
|
+
**实测教训(当轮发现并修正)**:首版把"机器起的名字"设为**不上锁**,结果重启前跑在旧代码里的概述器一轮增量重建,把 AI 标题**覆盖回"首句硬截 46 字"的半句**(库里 title 长度上限实锤 = **46**,正是旧上限)。锁的语义因此定为「**这个名字已经定过,机器不得自动重写**」,而非"只有主人能改"。
|
|
397
|
+
|
|
398
|
+
### 二、记忆中心看得见(D8)
|
|
399
|
+
|
|
400
|
+
- **「热度」排序此前是假排序**:`ORDER BY heat DESC`,而 `heat` 列恒为 0(L1 在运行时才算热度、从不落库)⇒ 点了等于没排。改用真实落库的 `hit_count DESC, importance DESC, updated_at DESC`;
|
|
401
|
+
- **筛选栏带计数**:新增 `GET /memories/sources`,显示为「历史网页端 (1523)」「DSH 会话 (60)」「文件导入 (16)」—— 否则 95% 的库在默认视图里沉底,主人无从知道库里有什么;
|
|
402
|
+
- 顺手修:`/memories/pin` 与 `/memories/delete` 原写死 `source === 'dsh' ? 'dsh' : 'dsweb'`,会把 `import` 源的置顶/删除误打到 dsweb 上(已改为来源白名单)。
|
|
403
|
+
|
|
404
|
+
### 三、导入诚实化(S5)
|
|
405
|
+
|
|
406
|
+
**旧症状:真正的 ChatGPT 导出,界面报「✓ 导入完成」,而对话正文一条都没进来。**
|
|
407
|
+
|
|
408
|
+
- **ChatGPT 原生 mapping 现在真能解析**:支持 `author.role`(`user`/`assistant`)+ `content.parts[]`,**且不再假设根节点叫 `root`** —— ChatGPT 导出的根是 `parent: null` 的随机 uuid 节点,旧实现 `walk('root')` 因此解析出 **0 轮**;
|
|
409
|
+
- **主链回溯**:有 `current_node` 时沿 `parent` 回溯取主对话链,避免把"重新生成"的多个分支重复入档;
|
|
410
|
+
- **秒级 epoch 归一**:ChatGPT 的 `create_time` 是**秒**(浮点)、DeepSeek 的是毫秒 —— 旧实现一律当毫秒,于是 ChatGPT 导出的时间全落到 **1970**。现按数量级判定(>1e11 毫秒 / >1e8 秒);
|
|
411
|
+
- **字段别名补齐**:`create_time` / `update_time` / `conversation_id`;
|
|
412
|
+
- **`degraded` 覆盖 `mapping` 分支**:旧判定只看 `messages` 数组,所以"mapping 树解析出 0 轮"被当成**成功**。现在只要"声称有内容却解析不出任何轮次"就标记 degraded,**并带原因**;
|
|
413
|
+
- **界面对主人说实话**:有壳无肉时不再显示「✓ 导入完成」,改为「⚠ 导入不完全:… 其中 N 条只有标题、没有对话正文」并给出原因 —— 旧文案把"解析不出对话"说成「降级为轻量」,让人以为一切正常;
|
|
414
|
+
- 导入标题也接入 D2 的启发式(剥称呼前缀、句读处收尾),不再硬截 46 字。
|
|
415
|
+
|
|
416
|
+
### 四、小助手地址可配置(S7)
|
|
417
|
+
|
|
418
|
+
小助手(本机小模型)的地址此前**硬编码在代码里**(`ASSISTANT_DEFAULT`),发布版还得靠发布脚本做字符串替换才不带私有地址 —— 换台机器、换个端口就得改代码。
|
|
419
|
+
|
|
420
|
+
现在**三级覆盖**,逐字段就近取值:
|
|
421
|
+
|
|
422
|
+
1. **界面配置**(最高优先):记忆中心 → 接入历史 → 「小助手地址」填地址与模型名 → 「保存地址」;旁边「测试连通」当场探活。写进 `settings.json` 的 `assistant` 段(`POST /assistant/config` 只接受 `baseUrl` / `model` 两个键,不开放任意设置写入);
|
|
423
|
+
2. **环境变量**:`DSH_LING_ASSISTANT` / `DSH_LING_ASSISTANT_MODEL`(部署级);
|
|
424
|
+
3. **内置默认**:`http://127.0.0.1:11434/v1` + `qwen3.5:9b` —— loopback,**公开仓库里不再有任何私有网段地址**。
|
|
425
|
+
|
|
426
|
+
- `GET /assistant/config` 如实回报**来源**(界面配置 / 环境变量 / 内置默认),界面照实显示"这个地址是哪来的";
|
|
427
|
+
- 生效面:`/memories/retitle`、`/dsweb/summary/preview`、`/dsweb/summary/run` 全部改走三级解析;
|
|
428
|
+
- 发布脚本 `publish-sync.mjs` 的替换表**清空**(比对/复制两侧的机制保留,将来若再有"源与发布副本故意不同"的合法需求只需加一条)。
|
|
429
|
+
|
|
430
|
+
### 五、测试
|
|
431
|
+
|
|
432
|
+
新增 `tests/retitle.test.mjs`(启发式 / 模型输出清洗 / 上锁后概述器不覆盖 / 排序 / 来源计数)与 `tests/assistant.test.mjs`(三级覆盖 / 默认值中性化护栏 / 探测失败路径),测试套件 23 → **25** 套,全绿;`importfile` 套新增 6 组 S5 断言(ChatGPT 形态 / 主链回溯 / 秒级时间 / degraded 判定与原因 / 汇总)。
|
|
433
|
+
|
|
434
|
+
## 1.2.2
|
|
435
|
+
|
|
436
|
+
**守卫加固 ③④ · 局域网不再自动信任 · 人格补丁字段白名单**
|
|
437
|
+
|
|
438
|
+
### 一、来源栅栏收紧(加固 ③)
|
|
439
|
+
|
|
440
|
+
1.2.1 的来源栅栏把**本机所有 LAN 地址无条件并入受信集合** —— 于是"同网段任何设备 + 按同一算法算出的 cookie 名"即可通过,而当时那把锁只校验 cookie **名字**、不校验值,等于把档案摆在了网段里。
|
|
441
|
+
|
|
442
|
+
- **本机 LAN 地址不再自动信任**:受信来源只剩 loopback(`127.0.0.0/8` / `::1` / `localhost`)与你在 `guard.trustedHosts` 里**显式声明**的 authority;
|
|
443
|
+
- 需要局域网直连时,打开 `guard.allowLan = true`(默认 `false`),或把该地址写进 `guard.trustedHosts`(支持 `1.2.3.4` 与 `1.2.3.4:3080` 两种粒度);
|
|
444
|
+
- **远端(隧道 / 反向代理)行为不变**:通道把请求改写为 loopback 形态后再发往本机,走的仍是恒受信的 loopback 分支;
|
|
445
|
+
- 新增 4 组断言 + 1 条源码护栏(旧写法 `[...lanAddresses(), ...trustedHosts]` 不得复活)。
|
|
446
|
+
|
|
447
|
+
### 二、人格补丁字段白名单(加固 ④)
|
|
448
|
+
|
|
449
|
+
`POST /persona` 原先把请求体**原样**深合并进 settings,于是 `{"guard":{"enforce":false}}` 足以持久化关停守卫(且被判定为"非人格补丁",连定型门都不会触发);`POST /import` 导入的外来档案同理。
|
|
450
|
+
|
|
451
|
+
- 补丁只接受 `persona` / `styles` 两棵子树及其白名单字段(17 + 2 个键),其余键**丢弃并如实回报** `ignored: [...]` 且写一行日志 —— 不是静默吞掉;
|
|
452
|
+
- 两条外部输入直通 settings 的路径(`/persona` 与 `/import`)全部收口;
|
|
453
|
+
- 防"白名单误杀"的断言以**合法字段是否仍能走到定型门**为准:若被误杀,补丁会变空、定型门不触发,反而返回成功 —— 这是本条最容易反向失败的地方。
|
|
454
|
+
|
|
455
|
+
### 三、小助手默认地址
|
|
456
|
+
|
|
457
|
+
"补摘要"默认调用本机小助手(Ollama 风格的 `/v1` 接口)。
|
|
458
|
+
|
|
459
|
+
- 默认地址改为 `http://127.0.0.1:11434/v1`(Ollama 默认端口)—— 谁都能在本机跑一个小模型直接接上;
|
|
460
|
+
- **没有本机小助手也照样能用**:补摘要仍可在界面上选择主模型来完成;
|
|
461
|
+
- 把地址做成运行时配置项(免改源码)排入下一版。
|
|
462
|
+
|
|
463
|
+
### 四、测试 23 套
|
|
464
|
+
|
|
465
|
+
## 1.2.1
|
|
466
|
+
|
|
467
|
+
**接口守卫加固 · 习惯候选更克制**
|
|
468
|
+
|
|
469
|
+
### 一、接口守卫加固(① 来源校验 + ② cookie 名字对撞)
|
|
470
|
+
|
|
471
|
+
原本 `/api/dsh-ling/*` 只判断"cookie 名字形如 `dsh-auth-…`",等于门框上画了把锁。现在接上 DSH 自己的锁:
|
|
472
|
+
|
|
473
|
+
- **来源栅栏**:Host 必须是 loopback / 本机 LAN 地址 / 你显式声明的 `guard.trustedHosts`;`Sec-Fetch-Site: cross-site` 直接拒;带 `Origin` 时其 host 必须等于请求 Host;
|
|
474
|
+
- **名字对撞**:期望 cookie 名 = `dsh-auth-` + base64url(sha256(规范化后的 Host))—— 与 DSH 派生同法,故**本地与远端登录都无行为变化**,被拒的是"随手编 `dsh-auth-x=` 的脚本"与"恶意页面的跨站请求";
|
|
475
|
+
- 拒绝返回 **403 + reason**(`untrusted-host` / `cross-site` / `origin-mismatch` / `no-cookie` / `empty-cookie` …)并写一行日志,便于排查;
|
|
476
|
+
- **逃生开关**:`settings.guard.enforce = false` 或环境变量 `DSH_LING_GUARD=off`(误判时不会把人锁在门外);
|
|
477
|
+
- 新模块 `lib/host/guard.js`(纯函数,可单测)+ 第 23 套测试(23 组断言,含 5 条源码护栏)。
|
|
478
|
+
|
|
479
|
+
> 未做:校验 cookie 值的签名(需读 DSH 凭据库、跨版本易碎)。因此**本机原生进程仍可调用**接口,挡住的浏览器侧伪造与跨站。
|
|
480
|
+
|
|
481
|
+
### 二、习惯待确认队列上限 5
|
|
482
|
+
|
|
483
|
+
- 待确认已有 5 条时,**直接驳回**新的生成请求(`reason: 'pending-full'`),并在面板与工具里说明原因 —— 不是静默丢弃,也不是无限排队;
|
|
484
|
+
- 面板标题显示 `(N/5 条待确认)`,满时追加一行提示;
|
|
485
|
+
- `settings.habits.pendingMax` 可调;第 17 套新增 7 组断言(含"被驳回的提议没有偷偷入队")。
|
|
486
|
+
|
|
487
|
+
### 三、习惯候选阈值收紧
|
|
488
|
+
|
|
489
|
+
- 同类纠正的认定由「≥3 次且跨 ≥2 会话」收紧为「**≥4 次且跨 ≥3 会话**」;
|
|
490
|
+
- 语义仍在两侧钉住:旧阈值下样本仍判定为模式、新默认下"3 次/2 会话"不再算模式,而"4 次/3 会话"照常命中。
|
|
491
|
+
|
|
492
|
+
### 四、测试 23 套
|
|
493
|
+
|
|
494
|
+
## 1.2.0
|
|
495
|
+
|
|
496
|
+
**记忆质量:网页端补摘要 · L1 摘要加权与命中记录 · L1 行压缩 · 归档语义收紧**
|
|
497
|
+
|
|
498
|
+
### 一、归档语义收紧(不再自动删数据)
|
|
499
|
+
|
|
500
|
+
- **归档即停更**:会话归档后不再刷新它的开场记忆条目,**原文一律保留**;该会话一旦有新对话即自动**复活**并重建条目;
|
|
501
|
+
- **取消自动清理** —— 归档不再删除任何概述;清理只保留人工入口:`node tools/clean-archived.mjs`(默认只列清单,`--write` 才动手,动手前自动 `VACUUM INTO` 备份),或在 DSH「设置 → 归档会话」里直接删;
|
|
502
|
+
- 新增单测第 21 套(归档清理 · 人工)。
|
|
503
|
+
|
|
504
|
+
### 二、L1 质量:摘要加权 + 命中记录
|
|
505
|
+
|
|
506
|
+
- 新增 `memory.summaryBonus`(默认 `0.25`):**只有真摘要**的行加分,`标题 — N 条消息` 这类自动计数摘要不算;
|
|
507
|
+
- **L1 命中记录**:首次定稿快照时记录命中,`hit_count` 从此有真实含义(此前恒为 0,热度公式里的"频次"腿长期失效);重建/失效/空闲刷新都不重复计数。
|
|
508
|
+
|
|
509
|
+
### 三、网页端历史补摘要(新)
|
|
510
|
+
|
|
511
|
+
- 新模块 `lib/host/dsweb-summary.js` + CLI `tools/backfill-dsweb-summary.mjs`(默认干跑;`--list` / `--write` / `--only-hit` / `--min-turns` / `--all`);
|
|
512
|
+
- 界面:记忆中心 →「概览与数据」新增卡片 —— **候选预览** / **双引擎选择**(本机小助手零成本 · 当前全局大模型)/ 进度轮询 / 重开面板自动续看;
|
|
513
|
+
- **绝不静默切换引擎**:本机小助手探测失败时明确报错,由你显式改选,不替你花钱;
|
|
514
|
+
- 首轮实测:候选 469 条 → **466 条全部成功、零成本、20.4 分钟**;
|
|
515
|
+
- 端点修复:本机小助手需走 Ollama 原生 `/api/chat` + `think:false`(OpenAI 兼容端点关不掉思考,会把 token 预算吃光导致正文为空);
|
|
516
|
+
- 新增单测第 22 套(候选排序 / 正文组装与预算 / 清洗 / 探测失败路径 / 重试)。
|
|
517
|
+
|
|
518
|
+
### 四、L1 行压缩(两段结构 + 允许缺省)
|
|
519
|
+
|
|
520
|
+
- 有摘要 → `标题 —— 摘要第一句`(第一句按中文标点切;超 **110 字**时硬截并补 `…`);
|
|
521
|
+
- 无摘要 → 只出标题,**不截**(信息量优先);
|
|
522
|
+
- `l1MaxLineChars`(180)降级为"病态长标题兜底";新增配置 `memory.l1SummaryChars`(默认 110);
|
|
523
|
+
- 实测:同一记忆库下 L1 八条 **1218 字 → 1046 字**。
|
|
524
|
+
|
|
525
|
+
### 五、界面文案
|
|
526
|
+
|
|
527
|
+
- 聊天记录库路径缺失时的提示改为**指路式**(可在本卡片填,或先在上方「网页端历史增量扫描」栏填一次,路径会被复用)。
|
|
528
|
+
|
|
529
|
+
### 六、测试
|
|
530
|
+
|
|
531
|
+
- 新增第 21、22 套 → **共 22 套**。
|
|
532
|
+
|
|
533
|
+
## 1.1.0
|
|
534
|
+
|
|
535
|
+
**人格成长层:规矩 / 习惯二分 · 习惯生成器 · 长会话空闲刷新 · 注入面瘦身 · 代词**
|
|
536
|
+
|
|
537
|
+
### 一、规矩 / 习惯二分(注入面与面板同步)
|
|
538
|
+
|
|
539
|
+
- 原「惯例」一栏拆成两类:**规矩**(你的指令 —— 可直接增删,一句话即写;上限 12 条)与**习惯**(她长出来的 —— 只能"提议 → 你点头",上限 8 条);既有条目全部迁入规矩,零歧义;
|
|
540
|
+
- 注入面随之分两段:`[规矩](用户的指令,可直接追加)` / `[习惯](你自己长的,新增需用户确认)`;
|
|
541
|
+
- 每条规矩留痕 `{文本, 原话, 时间, 会话}` —— "经你同意"是可核查的事实,不是她的自称;
|
|
542
|
+
- **习惯不可直达**:会话内工具、语料建议、点踩建议三条入口全部走"提议 → 确认";删除 / 改写习惯需**手术门**(亲手敲承诺句),界面在未解锁时不显示删除入口。
|
|
543
|
+
|
|
544
|
+
### 二、会话内工具
|
|
545
|
+
|
|
546
|
+
- `rule_add`:把你说的话记为一条规矩 —— **必须带你的原句**(没有原话写不进去);
|
|
547
|
+
- `habit_propose`:她提出一条习惯候选(入待确认队列);`habit_resolve`:她对你提的习惯表态(接受 / 换说法 / 婉拒);
|
|
548
|
+
- 工具注册改为**三级兜底 + 重试**(直取 → 子 ctx → 每 1.5s 重试),失败也写留痕(`tools.registered`),不再"静默没有工具"。
|
|
549
|
+
|
|
550
|
+
### 三、习惯生成器(从相处里长出来)
|
|
551
|
+
|
|
552
|
+
- **通道 A(零模型)**:扫描历史里的重复纠正,同一模式 **≥3 次且跨 ≥2 个会话**才成为候选,并附证据(出现在几个会话 / 共几次);
|
|
553
|
+
- **通道 B(一次调用)**:「✨ 让她回想最近的相处」读 **200 条会话概述 + 800 条你的原话**(实测约 3.5 万字),一次最多提 5 条;**分轮轮询** —— 游标持久化,读完一圈回到最新,面板显示「第 K 轮 · 覆盖 X / 共 Y」;
|
|
554
|
+
- **默认推理档 `off`**:实测 `low` 档只产出思考、正文为空且慢十倍(21s vs 2s);空回复或报错时自动以 `off` + 裁短材料重试一次;
|
|
555
|
+
- 每次运行留诊断(`habits.reflect.last`):目标模型、材料字数、每次尝试的耗时与返回块类型;目标模型始终是**你当前选定的模型**。
|
|
556
|
+
|
|
557
|
+
### 四、长会话空闲刷新
|
|
558
|
+
|
|
559
|
+
- 长跑会话在**空闲边界**比对记忆版本:有变化且距上次刷新 ≥10 分钟 → 只重建**开场记忆**(不重排人格、不打断运行中);刷新前先跑本会话的增量概述,让"刚结束这一轮"也能进入;
|
|
560
|
+
- 内容判等(文本没变就不替换、不计数)+ 留痕 `/state.idleRefresh`;
|
|
561
|
+
- 修掉"重建丢关键词上下文"(关键词加成最高 +1.8,远大于热度项,会让排序悄悄漂移)。
|
|
562
|
+
|
|
563
|
+
### 五、注入面瘦身
|
|
564
|
+
|
|
565
|
+
- 身份句不再重复宿主已声明的平台名、模式块由两行合并为一行、语气标签缩短 → **每个模式 −46 字**;
|
|
566
|
+
- 开场记忆行:标题 / 摘要去重 + **单行上限 180 字**(可配)→ 同样预算下**从 3 条装到 8 条**,总字数反而减少;
|
|
567
|
+
- 来源标识修正:DSH 会话 id 一律以 `session-` 开头,原来取前 8 位会让所有行都显示 `session-`(等于无法溯源)→ 改为可区分的短 id。
|
|
568
|
+
|
|
569
|
+
### 六、代词(第三人称)
|
|
570
|
+
|
|
571
|
+
- 新增 `persona.pronoun`,默认 **她**;预设 **她 / 他 / TA / 它**,另有「自定义」(≤6 字);
|
|
572
|
+
- 跟随范围:三条 LLM 提示词(习惯回想 / 语气归纳 / 诞生仪式)、取材标签、面板文案;
|
|
573
|
+
- **`祂` 刻意不进预设**(中文里专指神祇,放进预设等于替用户宣称神性),自定义可填;
|
|
574
|
+
- 属契约类:定型后修改需手术门(界面随锁禁用)。
|
|
575
|
+
|
|
576
|
+
### 七、时间戳归一
|
|
577
|
+
|
|
578
|
+
- 写入侧统一存 ISO;读取侧容错解析(`"1789006881011.0"` 这类浮点串按 epoch 毫秒处理);
|
|
579
|
+
- 新增工具 `tools/repair-timestamps.mjs`(默认干跑,`--write` 落库,写入前自动 `VACUUM INTO` 备份);
|
|
580
|
+
- 已修复存量脏值:原文层 74 行 + 概述层 15 行 —— 修复前这些行的"新近度"恒为 1.0(永不衰减),长期霸占开场记忆前排。
|
|
581
|
+
|
|
582
|
+
### 八、测试
|
|
583
|
+
|
|
584
|
+
- 共 **20 套**(新增:时间戳归一 / 空闲刷新 / 规矩·习惯 / 习惯生成器 / 会话内工具 / 开场记忆行),`npm test` 一条命令跑完。
|
|
585
|
+
|
|
586
|
+
## 1.0.3
|
|
587
|
+
|
|
588
|
+
**行为调整:循环外模型调用跟随当前选择**
|
|
589
|
+
|
|
590
|
+
- **深摘要等循环外调用不再写死模型**:深摘要、诞生仪式、语气归纳、自述起草统一经 `llmOnce` 发起,现在一律使用当前选择(`agentDefaultModel.currentSelection()`)的 `provider` / `model` —— 也就是你此刻选定的那个模型;读不到时才退到兜底值,并在日志里明确标记"本次不是跟随当前选择",不静默替你指定;
|
|
591
|
+
- 循环外调用保持**低推理档**(`effort: 'low'`):它只做摘要与归纳,不需要高等级推理,与模式的推理档位无关;
|
|
592
|
+
- 单测补齐(`tests/deep.test.mjs`):捕获真实 `llm.stream` 请求,断言发出的 `provider` / `model` 等于当前选择、不等于兜底值、档位恒为 `low`,以及不可读时 `followed: false`;
|
|
593
|
+
- 仓库地址整理:README 的 `git clone` 与 `package.json` 的 `repository` / `homepage` / `bugs` 由旧名 `dsh-ling` 更新为 **`DSH-Ling`**(旧地址仍 301 可用)。
|
|
594
|
+
|
|
595
|
+
## 1.0.2
|
|
596
|
+
|
|
597
|
+
**行为调整:模式只同步推理等级,不改模型**
|
|
598
|
+
|
|
599
|
+
- **模式切换不再修改模型**:工作 / 生活模式只同步**推理等级**(工作 = `max`、生活 = `low`),模型一律沿用你当前选定的那个。平台 API(`sessionController.selectModel` 与 `agentDefaultModel.saveSelection`)要求 `provider` / `model` **必填**(只有 `reasoningEffort` 可选),因此实现方式是"**先读当前选择 → 原样带回 → 只替换档位**";**读不到当前模型时放弃同步档位,而不是替你指定模型**;
|
|
600
|
+
- 界面与文档口径同步:提示语由"平台默认模型"改为"推理等级";`DESIGN.md` 里模式相关的 6 处描述统一为"档位",默认映射只保留 `effort`;
|
|
601
|
+
- 新增 `.gitattributes`(`* text=auto eol=lf`,图片与压缩包标记二进制),消除 Windows `core.autocrlf` 造成的行尾假差异。
|
|
602
|
+
|
|
603
|
+
## 1.0.1
|
|
604
|
+
|
|
605
|
+
**文档与仓库治理**(无功能变更)
|
|
606
|
+
|
|
607
|
+
- `LICENSE`:Apache-2.0 附录的版权署名由官方占位补为 `Copyright 2026 NightPainters`;
|
|
608
|
+
- `.gitignore`:补齐永不入库的数据类型 —— `settings.json`(人格档案/承诺句)、`*.dshling.json`(导出的记忆包)、`*.jsonl.zstd`(会话转储)、`.env*`、`logs/`、`*.tmp`;
|
|
609
|
+
- `DESIGN.md`:修掉空标题,修正已过期的 schema 说明(v1 → 现为 v2),补"实现以代码为准"的口径说明;
|
|
610
|
+
- 版本号改为**单一来源**:`lib/index.js` 不再硬编码,改为读 `package.json`(`/health` 与 `/state` 返回的就是这里);
|
|
611
|
+
- 首版发布后的自检补齐:`tools/check-publish.mjs` 在上传前实跑(文件类型/体积/绝对路径/密钥特征)。
|
|
612
|
+
|
|
613
|
+
## 1.0.0
|
|
614
|
+
|
|
615
|
+
**首次公开发布**
|
|
616
|
+
|
|
617
|
+
- **三层记忆**:L0 人格档案(每次对话恒定注入)/ L1 开场记忆(从历史里按相关度挑选的概述,有预算)/ L2 会话内滚动;
|
|
618
|
+
- **三条接入通道**:网页端聊天记录库增量扫描 · 本机 DSH 会话扫描 · ChatGPT / 通用 JSON·JSONL 导出文件导入;每次导入留一笔账(导入记录);
|
|
619
|
+
- **深摘要**:长会话逐条深摘,短会话以原文片段参与,后台串行、进度可见;
|
|
620
|
+
- **诞生仪式**:按"生活与情感优先"加权抽样 → 自述 / 名字 / 语气建议 / 相处观察,**全部草稿,人审后才落盘**;
|
|
621
|
+
- **人格定型锁**:契约类字段(名字 / 称呼 / 底线 / 世界观)改动需**亲手敲承诺句**(禁粘贴),进变更记录可回滚;自性类字段(语气 / 风格 / 自述 / 惯例)锁定下可自由生长;
|
|
622
|
+
- **被动时间锚**:每次开口前注入 `[现在] 日期 周X 时间 · 时段` 与距上次对话的间隔,称呼跟随用户设定的 userTitle;命中纪念日追加问候;
|
|
623
|
+
- **工作 / 生活双模式**:同一份人格档案,两套称呼、语气、规则与记忆权重;
|
|
624
|
+
- **成长回路**:点踩 → 反馈队列 → 惯例建议 → 采纳入档;「让她读记忆、归纳语气」一键成长;
|
|
625
|
+
- **全本地**:记忆库为本地 SQLite,插件不上传任何数据;
|
|
626
|
+
|
|
627
|
+
13 套测试(人格组装 / 冻结门 / 记忆库 / L1 选择 / 导出合并 / 文件导入 / 导入记录 / 深摘要 / 诞生仪式 / 语气 / 时间锚 / 捕获差分 / 契约形状)全绿。
|