@nightpainters/dsh-ling 1.6.1 → 1.7.0
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 +207 -41
- package/DESIGN.md +121 -29
- package/PHILOSOPHY.md +184 -0
- package/README.md +112 -21
- package/cordis.patch.yml +32 -2
- package/lib/client.js +2520 -55
- package/lib/host/agent-tools.js +286 -0
- package/lib/host/api.js +612 -61
- package/lib/host/archive.js +3 -3
- package/lib/host/autotree.js +2 -2
- package/lib/host/clock-skill.js +1 -1
- package/lib/host/config.js +38 -0
- package/lib/host/deep-index-skill.js +9 -0
- package/lib/host/deepsummary.js +75 -2
- package/lib/host/dsweb-summary.js +6 -3
- package/lib/host/guard.js +53 -0
- package/lib/host/inject.js +12 -2
- package/lib/host/l1.js +59 -10
- package/lib/host/library-checker.js +552 -0
- package/lib/host/library-import-engine.js +211 -0
- package/lib/host/library-import.js +911 -0
- package/lib/host/library-runtime.js +199 -0
- package/lib/host/library-sqlite.js +344 -0
- package/lib/host/library-store.js +1714 -0
- package/lib/host/memory.js +89 -30
- package/lib/host/persona.js +13 -3
- package/lib/host/rawlog.js +10 -5
- package/lib/host/recall-search.js +192 -17
- package/lib/host/snapshot.js +2 -1
- package/lib/host/surgery.js +7 -2
- package/lib/host/tools.js +506 -31
- package/lib/host/util.js +1 -1
- package/lib/index.js +120 -81
- package/package.json +11 -5
- package/tools/deep-import-docs.mjs +1210 -0
package/lib/host/memory.js
CHANGED
|
@@ -9,13 +9,18 @@ import { ensureDir, sha256Text, utcIso, mergeDeep, toIso, toEpochMs } from './ut
|
|
|
9
9
|
// 且把 memory 当参数传(依赖注入)⇒ memory.js 引它**不成环**。
|
|
10
10
|
import { archiveBefore } from './archive.js';
|
|
11
11
|
|
|
12
|
-
const SCHEMA_VERSION = 17;
|
|
12
|
+
const SCHEMA_VERSION = 17;
|
|
13
|
+
/** 作废哨兵(`deep_item.superseded_by` 的第三个取值,2026-10-15)。
|
|
14
|
+
* 为什么用哨兵而不是新列:所有"排除已被取代"的读路径(开场选择 / recall / 面板)
|
|
15
|
+
* 判的都是 `superseded_by != ''` —— 哨兵让**那些路径一处都不用改**就自动不再挑它;
|
|
16
|
+
* 而"为什么作废"落在 kv `deep.retract:<id>`(人可读、可审计),不污染表语义。 */
|
|
17
|
+
export const DEEP_RETRACTED = 'retracted'; // v17:deep_item.durability(条目「硬度」,1.6,2026-10-04 —— 主人原话「同会话使用硬数量上限来卡吗,这可能有点机械,能不能做成从根源上判断这些条目到底是临时的还是永久的」⇒ 病根不是"同一会话进了太多条",而是**临时的东西被判成了长期记忆**);v16:deep_item(深层库条目,1.6 步 2,2026-10-04 —— 纯新增表,DDL 的 IF NOT EXISTS 即迁移;与记忆树并立、条目不被枝隶属);v15:inject_log(投递留痕,1.6-A,2026-10-03 —— 纯新增表,DDL 的 IF NOT EXISTS 即迁移);v14:dsh_turns_raw.source(来源从"推断"变"记录",2026-10-01 主人定「来源也加个列吧」—— 旧判据是"裸 id = DSH 会话",边界是"将来若出现不带冒号的新来源名,物理上分不出来");v13:forgotten(软遗忘标记 —— 永远不彻底删除,先归档再打标记);v12:branch_log.actor(G4 器灵侧通道 —— 工具改的树必须记在"鱼姬"名下,不再一律记成主人);v11:vein_suggestions(主脉提炼审核队列 R7 —— 设计稿 §5:提炼必须走审核,不直接进记忆);
|
|
13
18
|
// v10:tree_snapshot.kind('manual'|'auto',审计 A#9 —— 自动备份与手动快照此前外表完全一致,删错了就没有撤回路径);v9:tree_snapshot(树的保存/恢复 R1);v8:conv_branch(非会话源的枝归属覆盖层,方案 A);v7:branch_log(复盘留痕);v6:vein_link + memory_conflict(D9-b 树的生长);v5:branch(记忆分枝 D9-a);v4:title_by(定名者:user/ai);v3:title_locked;v2:conv_overview.source 放开 CHECK
|
|
14
19
|
|
|
15
20
|
/** 主干枝 id(D9-a 记忆分枝,2026-09-18):存量会话与记忆天然属于主干。 */
|
|
16
21
|
export const TRUNK_ID = 'trunk';
|
|
17
22
|
|
|
18
|
-
/** 血缘档位(
|
|
23
|
+
/** 血缘档位(用户 2026-09-18 定档 1.0/0.7/0.4)。
|
|
19
24
|
* 取"最弱环"单值,不做连乘 —— `0.7^5=0.168`、`0.4^5=0.010`,连乘会让深枝等于从记忆里消失,
|
|
20
25
|
* 且每层舍入都会乘进后面。单值 + 有界,权重才可解释、可复现。 */
|
|
21
26
|
export const LINEAGE_SAME = 1.0; // 同枝
|
|
@@ -167,7 +172,7 @@ CREATE TABLE IF NOT EXISTS branch (
|
|
|
167
172
|
fork_at TEXT,
|
|
168
173
|
fork_seq INTEGER,
|
|
169
174
|
name_locked INTEGER NOT NULL DEFAULT 0,
|
|
170
|
-
--
|
|
175
|
+
-- weight_scale 自 1.6.2 起**参与检索打分**(见 setBranchWeight);visibility 仍是预留字段(只存与显示),
|
|
171
176
|
-- **不参与任何打分**(L1 的 score 只看血缘档位 × 矛盾系数)。让它们真正生效会改变检索排序语义,
|
|
172
177
|
-- 须主人拍板后再动;在那之前返回体里带 applied=false 标记,免得界面把"存了"当成"生效了"。
|
|
173
178
|
weight_scale REAL NOT NULL DEFAULT 1.0,
|
|
@@ -304,7 +309,13 @@ CREATE INDEX IF NOT EXISTS inject_log_session ON inject_log(session_id);
|
|
|
304
309
|
-- 且**枝侧可反向引用条目**(气根供养树):一条枝能列出供养过它的条目,而条目的增删**不改动树结构**。
|
|
305
310
|
CREATE TABLE IF NOT EXISTS deep_item (
|
|
306
311
|
id TEXT PRIMARY KEY, -- 'di:' + uuid(与记忆树 br: 同风格;带前缀便于眼认)
|
|
307
|
-
kind TEXT NOT NULL, --
|
|
312
|
+
kind TEXT NOT NULL, -- 承诺 | 决定 | 事实 | 偏好 | 关系 | 纪律(中文,1.6 实存值)
|
|
313
|
+
-- ⚠️ **纪律**是 2026-10-05 夜主人裁决新增的第六类(见 §D 的裁定):
|
|
314
|
+
-- 与"决定"的区别是**决定管一次、纪律管以后每一次**。进库三条判据
|
|
315
|
+
-- (缺一条就退回收 kv / plans,不许进库):① 来源是**主人授权的一句指令**;
|
|
316
|
+
-- ② 作用对象是**行为**(与 README R-3 的"规矩"同族);③ **违反时能被判据红出来**
|
|
317
|
+
-- —— 答不出"哪条判据会红"就不是纪律,只是一句话(与 F-26 同一条线)。
|
|
318
|
+
-- 注:列本身**无 CHECK 约束**(TEXT NOT NULL),故加这一类**不需要改 schema**。
|
|
308
319
|
text TEXT NOT NULL, -- 一句话 ≤80 字:它是**装配单元**(开场要从里面挑),必须独立读懂
|
|
309
320
|
at TEXT NOT NULL DEFAULT '', -- 发生时间(取自原文那一轮;UTC ISO —— 与 dsh_turns_raw.ts 同口径)
|
|
310
321
|
conv_id TEXT NOT NULL DEFAULT '', -- 来源会话(裸 id 或带命名空间前缀,与 conv_overview.conv_id 同口径)
|
|
@@ -321,6 +332,9 @@ CREATE TABLE IF NOT EXISTS deep_item (
|
|
|
321
332
|
-- ⚠️ ephemeral **不进开场**(选择侧过滤掉),但**照旧留在库里、recall 搜得到**:
|
|
322
333
|
-- 判严的代价远小于判松了(判松 = 每次开口都在推一条早失效的临时决定)。
|
|
323
334
|
superseded_by TEXT NOT NULL DEFAULT '', -- 被哪条取代 —— 沿用老规矩「只追不替」:旧的留痕、不删
|
|
335
|
+
-- 取值三态:'' = 在役 / 'di:…' = 被这条取代 /
|
|
336
|
+
-- 'retracted' = **作废**(无接替者,2026-10-15 用户拍板;
|
|
337
|
+
-- 理由落 kv「deep.retract:<id>」,见 DEEP_RETRACTED)
|
|
324
338
|
origin TEXT NOT NULL DEFAULT 'auto', -- auto(提炼) | user(主人手写);origin='user' 的不许被自动覆盖
|
|
325
339
|
branch_id TEXT NOT NULL DEFAULT '', -- 气根:空 = 未挂枝;非空 = 被这条枝反向引用(不参与树的层级计算)
|
|
326
340
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
@@ -396,7 +410,7 @@ function migrateTitleCols(db) {
|
|
|
396
410
|
}
|
|
397
411
|
}
|
|
398
412
|
|
|
399
|
-
/** v4→v5:记忆分枝(D9-a,2026-09-18
|
|
413
|
+
/** v4→v5:记忆分枝(D9-a,2026-09-18 用户定方向)。
|
|
400
414
|
* session_meta 加 branch_id(默认 'trunk' —— 存量会话天然属主干,迁移只填一列);
|
|
401
415
|
* 建 branch 表并保证主干行存在。**记忆行不冗余存 branch**,靠 session → branch 推导:
|
|
402
416
|
* 迁移成本最低,且枝归属变化时无需回写海量记忆行。 */
|
|
@@ -637,12 +651,11 @@ export class MemoryStore {
|
|
|
637
651
|
|
|
638
652
|
/** 全部枝(含主干)。
|
|
639
653
|
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
* 让界面与调用方知道"调了它也没用",避免把预留字段当成已生效的旋钮。 */
|
|
654
|
+
* ✅ **A-5(2026-10-05 夜 · 主人拍板走方案 b):`weightScale` 已接回检索打分** ——
|
|
655
|
+
* L1 的分数是 `raw × 血缘档位 × 矛盾系数 × 枝系数`,故这里如实标 `weightScaleApplied: true`。
|
|
656
|
+
* ⚠️ `visibility` **仍是预留字段**(本次只拍了权重):它只存只显示,标 `visibilityApplied: false`,
|
|
657
|
+
* 界面不要给它做"生效中"的暗示。
|
|
658
|
+
* 历史:审计 A#11(2026-09-22)曾把两者一起定为"只存只显示";本次只解开权重那一半。 */
|
|
646
659
|
listBranches() {
|
|
647
660
|
try {
|
|
648
661
|
return this.db.prepare('SELECT * FROM branch ORDER BY created_at, id').all().map((r) => ({
|
|
@@ -655,8 +668,8 @@ export class MemoryStore {
|
|
|
655
668
|
nameLocked: Number(r.name_locked || 0),
|
|
656
669
|
weightScale: Number(r.weight_scale ?? 1),
|
|
657
670
|
visibility: String(r.visibility || 'lineage'),
|
|
658
|
-
//
|
|
659
|
-
weightScaleApplied:
|
|
671
|
+
// A-5:权重已生效(见方法注释);visibility 仍为预留。
|
|
672
|
+
weightScaleApplied: true,
|
|
660
673
|
visibilityApplied: false,
|
|
661
674
|
status: String(r.status || 'active'),
|
|
662
675
|
createdAt: r.created_at ? String(r.created_at) : null,
|
|
@@ -936,11 +949,12 @@ export class MemoryStore {
|
|
|
936
949
|
|
|
937
950
|
/** 复盘期权重调节(平时只读 —— 设计稿 §3.3)。scale 夹在 [0,2]。
|
|
938
951
|
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
* `
|
|
943
|
-
*
|
|
952
|
+
* **A-5(2026-10-05 夜 · 主人拍板走方案 b):`weight_scale` 已接回检索打分** ——
|
|
953
|
+
* L1 = `raw × 血缘档位 × 矛盾系数 × 枝系数`。
|
|
954
|
+
* 历史:审计 A#11(2026-09-22)曾把**权重与可见性一起**定为"只存只显示";2026-10-05 只解开权重那一半
|
|
955
|
+
* (可见性仍是预留字段,见 `listBranches`)。
|
|
956
|
+
* ⚠️ 别再把权重这半边叫"预留字段"(2026-10-06 内部台账核对抓到:上面这行旧的"预留字段"与这行
|
|
957
|
+
* "已接回打分"**在同一个注释块里自相矛盾**,读第一行的人会判它无效)。 */
|
|
944
958
|
setBranchWeight(branchId, scale) {
|
|
945
959
|
const bid = String(branchId || '');
|
|
946
960
|
// ⚠️ 先判**类型**再取数(蓝队 2026-10-04):`Number(null) === 0`、`Number(true) === 1`、
|
|
@@ -954,7 +968,9 @@ export class MemoryStore {
|
|
|
954
968
|
this.db.prepare('UPDATE branch SET weight_scale=?, updated_at=? WHERE id=?').run(clamped, utcIso(), bid);
|
|
955
969
|
return {
|
|
956
970
|
ok: true, id: bid, weightScale: clamped,
|
|
957
|
-
applied:
|
|
971
|
+
applied: true,
|
|
972
|
+
note: '已生效:该枝的结论在记忆开场里按此系数加权(1.0 = 不偏不倚;⚠️ **0 = 该枝结论不进开场** —— '
|
|
973
|
+
+ '它会被当成"算不出正分"直接排除,所以 0 不是"只是排后面")',
|
|
958
974
|
};
|
|
959
975
|
}
|
|
960
976
|
|
|
@@ -1012,7 +1028,7 @@ export class MemoryStore {
|
|
|
1012
1028
|
|
|
1013
1029
|
// ---- 软遗忘(Part G G2,2026-09-25) ----
|
|
1014
1030
|
//
|
|
1015
|
-
// 原则(
|
|
1031
|
+
// 原则(用户 2026-09-25 定):**永远不做彻底删除**。遗忘 = 先把原数据归档落盘(原文 + 概述 +
|
|
1016
1032
|
// 改动日志),再在库里打一个标记,召回路径据标记排除。行仍在库里、归档文件在磁盘上 ——
|
|
1017
1033
|
// 两条独立还原路径,任何一条都能反悔。
|
|
1018
1034
|
//
|
|
@@ -1142,7 +1158,7 @@ export class MemoryStore {
|
|
|
1142
1158
|
*
|
|
1143
1159
|
* **调用方必须先归档当前状态** —— 本方法只管写,不做任何备份。
|
|
1144
1160
|
* 回灌的危险性高于遗忘:遗忘会留下"少了一条"的可见痕迹,而回灌是**往里加东西**,
|
|
1145
|
-
* 加错了很难察觉(
|
|
1161
|
+
* 加错了很难察觉(用户 2026-09-25:「backfill 比 forget 危险多了,因为它更难以觉察」)。
|
|
1146
1162
|
*
|
|
1147
1163
|
* 两道保护:① 写回的概述带 `origin` 标记(默认 `backfill-v1`),界面上看得出"这条是灌回来的";
|
|
1148
1164
|
* ② 不传 `keepUserState:false` ⇒ **不动主人留下的命中数与置顶**,回灌只补内容、不夺用户态。
|
|
@@ -1373,7 +1389,7 @@ export class MemoryStore {
|
|
|
1373
1389
|
// 换行能穿透引号、在系统提示里自造独立行(实测:note 塞 `\n\n【系统】…` 即可脱离框架)。
|
|
1374
1390
|
// 显示面修正(2026-09-22 实测):原实现对一切"查不到名字的 id"回退成「(已删枝 …)」,
|
|
1375
1391
|
// 但 branch_log 的 before/after **不只装枝 id** —— snapshot 装的是快照号(#9)、
|
|
1376
|
-
// assign 装的是记忆条目 id(026ebd86),二者都被误标成"已删枝"(
|
|
1392
|
+
// assign 装的是记忆条目 id(026ebd86),二者都被误标成"已删枝"(用户截图所见:
|
|
1377
1393
|
// `snapshot:主干 → (已删枝 #9)` 实际一根枝都没删)。先按 id 形态分流再回退查名。
|
|
1378
1394
|
const nm = (v) => {
|
|
1379
1395
|
const k = String(v || '');
|
|
@@ -1490,7 +1506,7 @@ export class MemoryStore {
|
|
|
1490
1506
|
}
|
|
1491
1507
|
}
|
|
1492
1508
|
|
|
1493
|
-
/** 尚未同步给器灵的改动笔数(复盘守门,2026-09-22
|
|
1509
|
+
/** 尚未同步给器灵的改动笔数(复盘守门,2026-09-22 用户:「动记忆树不能是随意的」)。
|
|
1494
1510
|
*
|
|
1495
1511
|
* 判据:自器灵**上一次开口**(注入推进水位)以来累积了多少笔结构改动。
|
|
1496
1512
|
* 水位取 `branch_log_wm.last`(由 `inject.js` 的 advanceLogWatermark 在投递时写入),
|
|
@@ -1511,7 +1527,7 @@ export class MemoryStore {
|
|
|
1511
1527
|
let pending = 0;
|
|
1512
1528
|
let maxId = 0;
|
|
1513
1529
|
try {
|
|
1514
|
-
// 'snapshot'(保存快照)不计入待交代笔数(2026-09-25
|
|
1530
|
+
// 'snapshot'(保存快照)不计入待交代笔数(2026-09-25 用户:「单纯保存不弹修改」)。
|
|
1515
1531
|
// 依据:保存快照**不改树**——它只是把当前结构另存一份,是"大改之前的保险",
|
|
1516
1532
|
// 不是"改动"。把它算成债会让主人每存一次就看到「⚠ 有 N 笔改动还没跟器灵说过」,
|
|
1517
1533
|
// 而实际上树一个字节没变。留痕仍写进 branch_log(审计价值保留),只是不推进债。
|
|
@@ -1582,7 +1598,7 @@ export class MemoryStore {
|
|
|
1582
1598
|
if (p && n.id !== TRUNK_ID) p.children.push(n);
|
|
1583
1599
|
else roots.push(n);
|
|
1584
1600
|
}
|
|
1585
|
-
// 层级 = **计算属性**(
|
|
1601
|
+
// 层级 = **计算属性**(用户 2026-09-19 定):按相对深度切,树长高时自动重算,**零回写**。
|
|
1586
1602
|
// 于是"几条主脉并成一条更大的主脉 ⇒ 原节点自动降级"是视图效果,不需要回写任何一行记忆 ——
|
|
1587
1603
|
// 这正是"任何树操作都不改写记忆内容"这条核心不变量能成立的原因。
|
|
1588
1604
|
// 档位:0=主干(恒 1 条);(0,0.25]=主脉;≤0.62=枝;其余=叶 ⇒ 最多 1 主干 / 2 主脉 / 3 枝 / 4 叶。
|
|
@@ -1620,7 +1636,7 @@ export class MemoryStore {
|
|
|
1620
1636
|
convBranch: this.db.prepare('SELECT source, conv_id, branch_id FROM conv_branch').all(),
|
|
1621
1637
|
links: this.db.prepare('SELECT from_branch, to_branch, kind, note FROM vein_link').all(),
|
|
1622
1638
|
};
|
|
1623
|
-
// 名字留空 → 自动命名;不写「未命名」(2026-09-22
|
|
1639
|
+
// 名字留空 → 自动命名;不写「未命名」(2026-09-22 用户:手工/自动 + 日期时间即可)
|
|
1624
1640
|
const nm = String(name || '').trim() || (k === 'auto' ? '自动快照 - ' : '手动快照 - ') + localStamp();
|
|
1625
1641
|
const r = this.db.prepare('INSERT INTO tree_snapshot (at,name,note,kind,data) VALUES (?,?,?,?,?)')
|
|
1626
1642
|
.run(now, nm.slice(0, 80), String(note || '').slice(0, 300), k, JSON.stringify(data));
|
|
@@ -2004,7 +2020,7 @@ export class MemoryStore {
|
|
|
2004
2020
|
}
|
|
2005
2021
|
|
|
2006
2022
|
/** 检索侧降权映射:`"source\\u0000convId" → 系数`。
|
|
2007
|
-
* 规则(
|
|
2023
|
+
* 规则(用户 2026-09-20 定):**复盘裁定的按裁定;未复盘的以最新为准**(旧的一方降权)。
|
|
2008
2024
|
* `duplicate` 不降权(两条内容相同,降谁都会丢信息);`dismissed` 一律不降权。 */
|
|
2009
2025
|
conflictDowngradeMap() {
|
|
2010
2026
|
const m = new Map();
|
|
@@ -2050,7 +2066,7 @@ export class MemoryStore {
|
|
|
2050
2066
|
* 已归档会话 id 集合(2026-09-21,D9-b 用)。
|
|
2051
2067
|
*
|
|
2052
2068
|
* 归档 = 主人主动封存、不再参与日常召回的那批会话。矛盾扫描必须排除它们,
|
|
2053
|
-
* 否则会拿几个月前已封存的对话互相判"矛盾",把待复盘队列塞满噪音(
|
|
2069
|
+
* 否则会拿几个月前已封存的对话互相判"矛盾",把待复盘队列塞满噪音(用户实测反馈)。
|
|
2054
2070
|
* 一次取全量而非逐行查 session_meta —— scan 里要判上千条,逐行查是 O(n) 次 SQL。
|
|
2055
2071
|
*/
|
|
2056
2072
|
archivedConvIdSet() {
|
|
@@ -2225,11 +2241,11 @@ export class MemoryStore {
|
|
|
2225
2241
|
if (!body) return { ok: false, reason: 'empty-text' };
|
|
2226
2242
|
const veinId = String(row.vein_id || '');
|
|
2227
2243
|
if (!veinId) return { ok: false, reason: 'no-vein' };
|
|
2228
|
-
// ── A-01(1.5.1
|
|
2244
|
+
// ── A-01(1.5.1 红蓝对抗;用户 2026-09-25 裁决「备份并覆盖」)──────────────────
|
|
2229
2245
|
// 采纳写入的键是 `(source='vein', conv_id=<主脉 id>)` ⇒ **同一主脉的两条候选先后采纳
|
|
2230
2246
|
// 会命中同一个 ON CONFLICT,后一条静默吃掉前一条**(无留痕、无归档 —— 数据真的没了;
|
|
2231
2247
|
// 红队 A 与红队 E 各自独立复现)。
|
|
2232
|
-
//
|
|
2248
|
+
// 用户的裁决与理由:主脉记忆是一种**概括**,它理应随主脉本身一起成长 ⇒ **覆盖**是对的;
|
|
2233
2249
|
// 但覆盖必须能回头 ⇒ 动手前把旧版**归档落盘**,归档失败就**拒绝覆盖**
|
|
2234
2250
|
// (与 forget 那条"归档失败绝不打标记"同一条纪律),并把结果回传调用方去记总变动日志。
|
|
2235
2251
|
let replaced = null;
|
|
@@ -2621,6 +2637,26 @@ export class MemoryStore {
|
|
|
2621
2637
|
return { ok: true, id: String(oldId), by: String(newId) };
|
|
2622
2638
|
}
|
|
2623
2639
|
|
|
2640
|
+
/** **作废**一条结论(2026-10-15,用户拍板「允许作废一条结论」)。
|
|
2641
|
+
* 与 supersede 的分工:取代 = "有一条新的接着";作废 = "这条不成立了、没有接替者"。
|
|
2642
|
+
* 实现**不删行**(老规矩「只追不替」):只把 `superseded_by` 标成哨兵 `retracted`。
|
|
2643
|
+
* WHY 落 kv:`deep.retract:<id>` = `{reason, by, at}` —— 过一阵要回答"当初为什么不要它"时,
|
|
2644
|
+
* 答案必须在库里,而不在某次已经折叠掉的会话里。
|
|
2645
|
+
* ⚠️ 已被取代/已作废的条目**再作废一次会被拒**(`already-retired`)——
|
|
2646
|
+
* 否则后一次的 reason 会把前一次的盖掉,留痕就变成"最后一次说的算"。 */
|
|
2647
|
+
retractDeepItem(id, { reason = '', by = 'user' } = {}) {
|
|
2648
|
+
const row = this.deepItemById(id);
|
|
2649
|
+
if (!row) return { ok: false, reason: 'not-found' };
|
|
2650
|
+
const prev = String(row.superseded_by || '');
|
|
2651
|
+
if (prev !== '') return { ok: false, reason: 'already-retired', superseded_by: prev };
|
|
2652
|
+
this.db.prepare('UPDATE deep_item SET superseded_by=?, updated_at=? WHERE id=?')
|
|
2653
|
+
.run(DEEP_RETRACTED, utcIso(), String(id));
|
|
2654
|
+
try {
|
|
2655
|
+
this.kvSet('deep.retract:' + String(id), JSON.stringify({ reason: String(reason), by: String(by), at: utcIso() }));
|
|
2656
|
+
} catch { /* 留痕失败**不改变**"作废已生效"这件事 —— 表已经改了,回执不许说谎 */ }
|
|
2657
|
+
return { ok: true, id: String(id), superseded_by: DEEP_RETRACTED, reason: String(reason) };
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2624
2660
|
/** 加固 +1。⚠️ **只该由"真被采用"调用** —— 注入不算(见列注释:注入就加分会形成自我强化回路)。
|
|
2625
2661
|
* 采用信号的首选 = `recall` 取过它指向的原文(那条路已在投递留痕面内)。 */
|
|
2626
2662
|
bumpDeepHit(id) {
|
|
@@ -2639,6 +2675,29 @@ export class MemoryStore {
|
|
|
2639
2675
|
return Number(this.db.prepare('SELECT COUNT(*) AS n FROM deep_item').get()?.n) || 0;
|
|
2640
2676
|
}
|
|
2641
2677
|
|
|
2678
|
+
/** 已退役(被取代或已作废)的条数 —— 与 deepItemCount 分开报:
|
|
2679
|
+
* "库里 411 条"和"现在还会被挑的是 408 条"是两件事,混着说会让人以为退役的还在参与。 */
|
|
2680
|
+
deepRetiredCount() {
|
|
2681
|
+
return Number(this.db.prepare("SELECT COUNT(*) AS n FROM deep_item WHERE superseded_by<>''").get()?.n) || 0;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/** 条目行 → **对外视图**(面板、工具、HTTP 三面共用一份)。
|
|
2685
|
+
* WHY 落在这里:api.js 的 `/deep/items` 与 tools.js 的 `list` 报的是**同一种东西**,
|
|
2686
|
+
* 各写一份映射必然漂(本仓反复吃过这个亏)⇒ 单一来源钉在库这一层,两个面都调它。
|
|
2687
|
+
* 故意**不**放 `db` 内部行:`superseded_by` 这种内部列也照发(面板要能看出"它退役了"),
|
|
2688
|
+
* 但字段名统一成稳定的一套,免得前端跟着列名漂。 */
|
|
2689
|
+
deepItemView(r) {
|
|
2690
|
+
const row = r || {};
|
|
2691
|
+
return {
|
|
2692
|
+
id: String(row.id ?? ''), kind: String(row.kind ?? ''), text: String(row.text ?? ''),
|
|
2693
|
+
durability: String(row.durability ?? ''),
|
|
2694
|
+
hit_count: Number(row.hit_count) || 0, pinned: Number(row.pinned) || 0,
|
|
2695
|
+
conv_id: String(row.conv_id ?? ''), src: String(row.src ?? ''), at: String(row.at ?? ''),
|
|
2696
|
+
superseded_by: String(row.superseded_by ?? ''), branch_id: String(row.branch_id ?? ''),
|
|
2697
|
+
created_at: String(row.created_at ?? ''),
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2642
2701
|
// ---- raw turns (DSH incremental capture, passive) ----
|
|
2643
2702
|
/** 追加一条原文轮次(幂等:(session_id,seq) 冲突时更新正文)。
|
|
2644
2703
|
* v14:`source` 记**这条原文属于哪个来源**(取值口径见 RAW_SOURCE_DSH 段)。缺省 null ⇒ 按 id
|
package/lib/host/persona.js
CHANGED
|
@@ -63,8 +63,8 @@ export const DEFAULT_SETTINGS = Object.freeze({
|
|
|
63
63
|
},
|
|
64
64
|
memory: {
|
|
65
65
|
l1Enabled: true,
|
|
66
|
-
l1BudgetTokens: 720, // 1.6 步 5:1200 → 720(「瘦身到现在的 60
|
|
67
|
-
l1MaxItems: 10, // 1.6:8 → 10(
|
|
66
|
+
l1BudgetTokens: 720, // 1.6 步 5:1200 → 720(「瘦身到现在的 60%」,用户 2026-10-04 拍)
|
|
67
|
+
l1MaxItems: 10, // 1.6:8 → 10(用户 2026-10-04:「8 条也可以考虑改 10 条,其中 2 条作为最硬的事实的席位」)
|
|
68
68
|
l1MaxLineChars: 180, // 兜底硬顶:只对病态长标题生效(截断优先落在句末)
|
|
69
69
|
l1SummaryChars: 110, // 摘要段预算:第一句超此长度才硬截并标 …(2026-09-16 与用户定案)
|
|
70
70
|
categoryWeights: { // mode -> 领域类别权重(D6,先验倾向)
|
|
@@ -85,7 +85,6 @@ export const DEFAULT_SETTINGS = Object.freeze({
|
|
|
85
85
|
// ⚠️ 这是**内存态**设置(`settings-file.js` 的 get() 每轮读),面板 update() 立即改 ⇒ 不用重启宿主。
|
|
86
86
|
sessionProgressLine: true,
|
|
87
87
|
},
|
|
88
|
-
updates: { applyWhileRunning: false }, // D4 语义固化:默认冻结
|
|
89
88
|
guard: { // /api 守卫(2026-09-16 加固 ①+②;2026-09-17 加固 ③)
|
|
90
89
|
enforce: true, // false = 关掉来源栅栏与名字对撞(逃生开关;亦可用 DSH_LING_GUARD=off)
|
|
91
90
|
trustedHosts: [], // 显式受信的 authority 列表;loopback 恒受信,本机 LAN 地址**不再自动信任**
|
|
@@ -106,6 +105,17 @@ export const DEFAULT_SETTINGS = Object.freeze({
|
|
|
106
105
|
baseUrl: '', // 空 = 落到环境变量 DSH_LING_ASSISTANT,再落到内置默认(loopback:11434)
|
|
107
106
|
model: '', // 空 = 落到环境变量 DSH_LING_ASSISTANT_MODEL,再落到 qwen3.5:9b
|
|
108
107
|
},
|
|
108
|
+
// 后台自动行为(2026-10-16 与用户定):这些行为**一直是开着的**,只是此前既没有文档、
|
|
109
|
+
// 也没有面板开关 —— 只能靠改插件配置(cfg)或读源码才知道。现在收进 settings.auto:
|
|
110
|
+
// 面板可读写,默认值与原硬编码**逐字相同**(关掉前请听清每行说明的后果)。
|
|
111
|
+
// 三级优先级:settings.auto.<键> > 插件配置 cfg.<旧键> > 这里(解析只在 deepsummary.resolveAuto 一处)。
|
|
112
|
+
auto: {
|
|
113
|
+
deepSummary: true, // 自动深摘总闸:关掉后只有你点「深摘」时才调用模型;已经深摘过的会话不受影响
|
|
114
|
+
deepIntervalMin: 30, // 自动深摘间隔(分钟,夹到 10..1440):调小 = 记忆更新更勤、更吃模型额度;调大 = 省额度
|
|
115
|
+
deepMaxPerRun: 2, // 每轮最多深摘几个会话(夹到 1..20):调大 = 一轮更久、一次花掉更多额度
|
|
116
|
+
bootProbeDelayMs: 12000, // 启动后多久做一次 LLM 探活(毫秒,夹到 0..600000):0 = 立即探活(宿主刚起时可能失败)
|
|
117
|
+
feedbackAuto: true, // 反馈成长回路(事件驱动):关掉后点踩/点赞不再自动长出规矩与习惯提议
|
|
118
|
+
},
|
|
109
119
|
dates: [], // 自定义纪念日 [{m,d,label,greeting}](内置 9-07 新生纪念日见 clock.js)
|
|
110
120
|
});
|
|
111
121
|
|
package/lib/host/rawlog.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-ling host — 直通原文(1.6 · P2 第一优先)。
|
|
2
2
|
//
|
|
3
|
-
// 为什么要有这个模块(2026-10-04
|
|
3
|
+
// 为什么要有这个模块(2026-10-04 用户定「第一优先级就是先把读原文的这个事儿给办了」):
|
|
4
4
|
// 器灵此前**没有任何正规通道**能读会话原文 —— 7 个工具里一个都读不到,
|
|
5
5
|
// 而 memory.db 的 `dsh_turns_raw` 是**残的**(本会话实测:库里 13 条 vs 磁盘 473 条,≈2.7%)。
|
|
6
6
|
// 真全文只躺在磁盘上: ~/.dsh/sessions/<项目目录>/<会话目录>/session.v4.jsonl.zstd
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
// 实测 **516 帧扫描 1ms + 全量解压解析 53ms**(相对暴力法快约 2200 倍)。
|
|
17
17
|
// 抄而不 import 的理由:不把插件绑到宿主内部包上(那是 P3 的劣,主人 2026-10-04 拍「先 P2」)。
|
|
18
18
|
import fs from 'node:fs';
|
|
19
|
-
import os from 'node:os';
|
|
20
19
|
import path from 'node:path';
|
|
21
20
|
import { zstdDecompressSync, constants as zc } from 'node:zlib';
|
|
22
|
-
import { isMemoryEligibleHeader } from './util.js'; // F3(2026-10-04):真人判据要按**会话级**分通道
|
|
21
|
+
import { isMemoryEligibleHeader, dshHome } from './util.js'; // F3(2026-10-04):真人判据要按**会话级**分通道
|
|
23
22
|
|
|
24
23
|
/** Zstandard 帧魔数(小端读作 UInt32LE 时的值)。 */
|
|
25
24
|
const ZSTD_MAGIC = 0xfd2fb528;
|
|
@@ -46,9 +45,15 @@ function findLogIn(dir) {
|
|
|
46
45
|
return null;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
/** 会话根目录:
|
|
48
|
+
/** 会话根目录: `$DSH_HOME/sessions`(未设时 `~/.dsh/sessions`)。
|
|
49
|
+
* ⚠️ 2026-10-05 修正(A-4,交办单最后一处漏点):本函数原先是 `join(os.homedir(), '.dsh', 'sessions')`
|
|
50
|
+
* —— 绕过 `$DSH_HOME`,是全仓最后一处这么写的地方(`backfill.js:45-51` 记着另两处早在
|
|
51
|
+
* 2026-09-29 就统一到 `dshHome()` 了)。不设 `DSH_HOME` 时两者等价,**缺陷因此长期隐身**;
|
|
52
|
+
* 一旦设了(导入测试台就是靠它做隔离,`DESIGN-导入测试台.md:25` 明写"`DSH_HOME` 层面隔离才有效"),
|
|
53
|
+
* 这里就会去读**真机**会话目录 —— 不报错、只读,但结论不可复现,还会把真历史灌进测试库。
|
|
54
|
+
* 现统一到 `dshHome()`(= `$DSH_HOME` 优先,否则 `~/.dsh`),与扫描侧/取原文侧同源。 */
|
|
50
55
|
export function sessionsRoot() {
|
|
51
|
-
return path.join(
|
|
56
|
+
return path.join(dshHome(), 'sessions');
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|