@nightpainters/dsh-ling 1.5.3 → 1.6.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.
@@ -19,7 +19,7 @@
19
19
  // 零行为变更:本文件的内容逐条搬自 `api.js` 原有 handler 的内联逻辑,搬动时**不改判定**,
20
20
  // 只把 `sendJson(res, X, Y)` 换成 `return { ...Y, status: X }`。
21
21
 
22
- import { TRUNK_ID } from './memory.js';
22
+ import { TRUNK_ID, BRANCH_NAME_MAX } from './memory.js';
23
23
 
24
24
  /** 枝列表 → id 索引 Map(原 `api.js:62` 的私有助手,搬来共用)。 */
25
25
  export const mapBranchList = (list) => new Map((Array.isArray(list) ? list : []).map((b) => [String(b.id), b]));
@@ -100,7 +100,7 @@ export function opBranchCreate(memory, args = {}) {
100
100
  if (kind !== 'branch' && kind !== 'vein') return bad('bad-kind');
101
101
  const parentId = String(args.parentId || TRUNK_ID);
102
102
  if (!memory.listBranches().some((b) => b.id === parentId)) return bad('no-parent');
103
- const name = String(args.name || '').trim().slice(0, 80);
103
+ const name = String(args.name || '').trim().slice(0, BRANCH_NAME_MAX);
104
104
  // 审计 B#4:空名枝会成为树里一个点不开的幽灵节点;旧版**没有任何删枝端点**,建错了只能改库。
105
105
  if (!name) return bad('empty-name');
106
106
  const id = kind === 'vein'
@@ -130,7 +130,7 @@ export function opBranchReparent(memory, args = {}) {
130
130
  if (parentId && !memory.listBranches().some((b) => b.id === parentId)) {
131
131
  if (String(args.createVein || '') === '1' || args.name) {
132
132
  parentId = memory.createVein({
133
- name: String(args.name || '新主脉').slice(0, 80),
133
+ name: String(args.name || '新主脉').slice(0, BRANCH_NAME_MAX),
134
134
  parentId: String(args.veinParent || TRUNK_ID),
135
135
  });
136
136
  } else {
@@ -159,21 +159,27 @@ export function opBranchReparent(memory, args = {}) {
159
159
 
160
160
  /** 改枝名(lock 默认 true ⇒ 起名即定名,机器不再覆盖)。 */
161
161
  export function opBranchRename(memory, args = {}) {
162
- const name = String(args.name || '').trim().slice(0, 80);
162
+ const name = String(args.name || '').trim().slice(0, BRANCH_NAME_MAX);
163
163
  if (!name) return bad('empty-name');
164
164
  const id = String(args.id || '');
165
165
  const beforeRow = memory.listBranches().find((b) => b.id === id) || null;
166
166
  if (!beforeRow) return bad('no-match'); // 审计 B#3
167
167
  const r = memory.renameBranch(id, name, { lock: args.lock !== false });
168
168
  if (r.ok) {
169
+ // ⚠️ D-T03(蓝队 2026-10-04 抓到这一处):**回执与留痕的名字必须来自库**,不能来自入参 ——
170
+ // 入参截到 `BRANCH_NAME_MAX`、`memory.renameBranch` 曾只存 60 ⇒ 旧写法让留痕写着 70 字、
171
+ // 库里其实只有 60 字。这里写后读回一次,与 `opBranchWeight` 同一口径;读不回来时退回 `r.name`。
172
+ const afterRow = memory.listBranches().find((b) => b.id === id) || null;
173
+ const stored = afterRow ? String(afterRow.name || '') : '';
169
174
  memory.logBranch(id, 'rename', {
170
175
  before: (beforeRow && beforeRow.name) || '',
171
- after: name,
176
+ after: stored || r.name || name,
172
177
  note: String(args.note || ''),
173
178
  actor: actorOf(args),
174
179
  });
180
+ return { ...r, name: stored || r.name || name, status: 200 };
175
181
  }
176
- return { ...r, status: r.ok ? 200 : 400 };
182
+ return { ...r, status: 400 };
177
183
  }
178
184
 
179
185
  /** 调枝系数(设计稿 §3.3:权重**仅复盘时**修改 —— 平时只读,由工具层的策略把关)。 */
package/lib/host/util.js CHANGED
@@ -117,7 +117,7 @@ export function mergeDeep(base, over) {
117
117
  return out;
118
118
  }
119
119
 
120
- /** 会话种类(记忆分枝 D9-a,2026-09-18 用户定方向)。
120
+ /** 会话种类(记忆分枝 D9-a,2026-09-18 尝生定方向)。
121
121
  * 背景:原 `isTopLevelSessionHeader` 把三类混成一个 false —— 子代理、委托、分叉**都不入库、不注入**。
122
122
  * 但分叉会话是主人显式开的**并行工作线**(工程上"先放下别的模块,专注做完这一个再回来"),
123
123
  * 它应当**有自己的记忆**(写入所属枝)且**能读祖先链**,而不是记忆真空。
package/lib/index.js CHANGED
@@ -17,6 +17,7 @@ import { registerFeedbackListener } from './host/feedback.js';
17
17
  import { probeLLM, runDeepPass, DEEP_CFG } from './host/deepsummary.js';
18
18
  import { registerLingTools } from './host/tools.js';
19
19
  import { registerClockSkill } from './host/clock-skill.js';
20
+ import { registerIndexSkill } from './host/deep-index-skill.js'; // 1.6 步 6:深层库索引层(一簇一行,走 skill)
20
21
  import { installAccessLog } from './audit/index.js';
21
22
 
22
23
  export const name = 'dsh-ling';
@@ -37,7 +38,39 @@ export function apply(ctx, config = {}) {
37
38
  ensureDir(dataDir);
38
39
 
39
40
  const settings = new SettingsFile(dataDir);
40
- const memory = new MemoryStore(join(dataDir, 'memory.db'));
41
+ // ⚠️ F4(蓝队 2026-10-04 实测 + 主人 2026-10-04 拍板「按错误码分流」):这里此前**没有 try** ⇒
42
+ // 只要构造抛(典型:另一个进程持写锁 —— 升级形态实测 16,445ms 后抛 `database is locked`),
43
+ // 异常就一路冒到 `apply()` ⇒ **8 个工具全不在、人格注入也没了**,而且**没有任何可指认面**。
44
+ // 那是 **fail-dead** 不是 fail-closed(本项目对 fail-closed 的定义 = 拒绝 + 给判据,见 B-05)。
45
+ // 分流规则:
46
+ // · errcode **5 / 6**(BUSY / LOCKED)= "等一会儿就能开" ⇒ 同步有界重试 3 次 × 2 秒;
47
+ // · **其余**(8 READONLY / 11 CORRUPT / 打不开文件…)= "真的打不开" ⇒ **不重试**,直接降级;
48
+ // 仍失败 ⇒ **降级加载**:本插件不注册任何东西(别的插件不受影响),并留一条**可执行**的 error。
49
+ const sleepSync = (ms) => {
50
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* 退化:不睡 */ }
51
+ };
52
+ const isLockErr = (e) => Number(e?.errcode ?? e?.code ?? -1) === 5 || Number(e?.errcode ?? -1) === 6
53
+ || /SQLITE_BUSY|SQLITE_LOCKED|database is locked/i.test(String(e?.message ?? e));
54
+ let memory = null;
55
+ let memoryErr = null;
56
+ for (let attempt = 0; attempt < 3 && !memory; attempt += 1) {
57
+ try {
58
+ memory = new MemoryStore(join(dataDir, 'memory.db'));
59
+ } catch (e) {
60
+ memoryErr = e;
61
+ if (!isLockErr(e)) break; // 非锁类:立即放弃重试(照旧"真的打不开")
62
+ if (attempt < 2) sleepSync(2000);
63
+ }
64
+ }
65
+ if (!memory) {
66
+ const dbFile = join(dataDir, 'memory.db');
67
+ console.error('[dsh-ling] 记忆库打不开,**本次以降级形态加载**(记忆 / 人格注入 / 器灵工具面全部不可用;'
68
+ + '其余插件不受影响)。原因:' + String(memoryErr?.message ?? memoryErr).slice(0, 300)
69
+ + '\n 怎么排查:① 多半是别的进程正持写锁 —— 等几秒后重载宿主;'
70
+ + '② 确认 ' + dbFile + ' 没被只读挂载或被独占;'
71
+ + '③ 若文件损坏,先从 ' + dataDir + ' 下的备份恢复。');
72
+ return;
73
+ }
41
74
  // invalidate 延迟绑定(避免环):onFlush 在事件时点才调用,彼时必已就绪
42
75
  let invalidateNow = (sid) => {};
43
76
  import('./host/inject.js').then((m) => {
@@ -70,8 +103,14 @@ export function apply(ctx, config = {}) {
70
103
  // E4 可取证访问日志:只挂两个全局钩子,core 侧补丁留的调用点见 lib/audit/patch-spec.js。
71
104
  // 补丁未打时钩子收不到调用 —— 安装器会在启动日志里明确喊出来,不允许静默不记。
72
105
  disposers.push(installAccessLog(ctx, cfg.accessLog || {}));
73
- // clock skill(精确时间按需查询,2026-09-23 用户定):取服务走与 tools 同款的三级兜底。
106
+ // clock skill(精确时间按需查询,2026-09-23 尝生定):取服务走与 tools 同款的三级兜底。
74
107
  disposers.push(...registerClockSkill(ctx));
108
+ // 深层库索引层 skill(1.6 步 6,2026-10-04):主人拍过「索引层 ≤400 字 · 一簇一行」,
109
+ // 承载形态选定为**整个索引层一个 skill** —— 目录只常驻 name + description(11 行簇目录),
110
+ // 正文按需展开;而 skill 目录按 sha256 比对、**内容不变就不重发** ⇒ 前缀稳定是白拿的。
111
+ // ⚠️ 不要拆成"每簇一个 skill":实测边际成本 ≈205 字/条,11 簇会把常驻目录面撑爆,
112
+ // 而那个池子正是工具面压缩要抢的同一个。
113
+ disposers.push(...registerIndexSkill(ctx));
75
114
  // 会话内工具(规矩直达 / 习惯提议):拿不到 tools 服务就退化为"只能用面板",绝不抛错。
76
115
  // 取服务用三级兜底(与 webServer 同一套路):直取 → ctx.inject 子 ctx → 定时重试。
77
116
  // 2026-09-16 教训:本插件 apply 可能早于 tools 服务挂载,一次性直取会静默失败 —— 工具从未注册。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nightpainters/dsh-ling",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "description": "器灵 · 跑在你自己电脑上的记忆与人格助手插件:三层记忆、从历史里长出来的人格档案、定型锁与自由生长",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -24,7 +24,7 @@
24
24
  "access": "public"
25
25
  },
26
26
  "scripts": {
27
- "test": "node tests/unit.mjs && node tests/access-log.test.mjs && node tests/archive.test.mjs && node tests/autotree.test.mjs && node tests/backfill.test.mjs && node tests/branch.test.mjs && node tests/assistant.test.mjs && node tests/capture.test.mjs && node tests/clock.test.mjs && node tests/clock-skill.test.mjs && node tests/deep.test.mjs && node tests/dswebsum.test.mjs && node tests/duty.test.mjs && node tests/export.test.mjs && node tests/feedback.test.mjs && node tests/forget-backfill.test.mjs && node tests/genesis.test.mjs && node tests/guard.test.mjs && node tests/habitgen.test.mjs && node tests/idle.test.mjs && node tests/importfile.test.mjs && node tests/importlog.test.mjs && node tests/l1lines.test.mjs && node tests/memories.test.mjs && node tests/mode.test.mjs && node tests/mode-service.test.mjs && node tests/mustache.test.mjs && node tests/retitle.test.mjs && node tests/rules.test.mjs && node tests/sessionline.test.mjs && node tests/suggest.test.mjs && node tests/summarize.test.mjs && node tests/times.test.mjs && node tests/tone.test.mjs && node tests/tools.test.mjs && node tests/tree.test.mjs && node tests/patch-spec.test.mjs && node tests/bodylimit.test.mjs && node tests/client-batch.test.mjs",
27
+ "test": "node tests/unit.mjs && node tests/access-log.test.mjs && node tests/archive.test.mjs && node tests/autotree.test.mjs && node tests/backfill.test.mjs && node tests/branch.test.mjs && node tests/assistant.test.mjs && node tests/capture.test.mjs && node tests/clock.test.mjs && node tests/clock-skill.test.mjs && node tests/deep.test.mjs && node tests/deepitem.test.mjs && node tests/dswebsum.test.mjs && node tests/duty.test.mjs && node tests/export.test.mjs && node tests/feedback.test.mjs && node tests/forget-backfill.test.mjs && node tests/genesis.test.mjs && node tests/guard.test.mjs && node tests/habitgen.test.mjs && node tests/idle.test.mjs && node tests/importfile.test.mjs && node tests/importlog.test.mjs && node tests/injectlog.test.mjs && node tests/l1lines.test.mjs && node tests/memories.test.mjs && node tests/mode.test.mjs && node tests/mode-service.test.mjs && node tests/mustache.test.mjs && node tests/retitle.test.mjs && node tests/rules.test.mjs && node tests/sessionline.test.mjs && node tests/suggest.test.mjs && node tests/summarize.test.mjs && node tests/times.test.mjs && node tests/tone.test.mjs && node tests/tools.test.mjs && node tests/recall.test.mjs && node tests/recall-search.test.mjs && node tests/tree.test.mjs && node tests/patch-spec.test.mjs && node tests/bodylimit.test.mjs && node tests/client-batch.test.mjs",
28
28
  "check": "node tools/check-install.mjs",
29
29
  "check:publish": "node tools/check-publish.mjs"
30
30
  },
@@ -61,6 +61,7 @@
61
61
  "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.2",
62
62
  "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.2",
63
63
  "@deepseek-ai/dsh-llm": ">=0.1.7-rc.2",
64
+ "@deepseek-ai/dsh-session-query": ">=0.1.7-rc.2",
64
65
  "@deepseek-ai/dsh-skill": ">=0.1.7-rc.2",
65
66
  "@deepseek-ai/dsh-system-prompt": ">=0.1.7-rc.2",
66
67
  "@deepseek-ai/dsh-tools": ">=0.1.7-rc.2"
@@ -90,6 +91,9 @@
90
91
  "@deepseek-ai/dsh-llm": {
91
92
  "optional": true
92
93
  },
94
+ "@deepseek-ai/dsh-session-query": {
95
+ "optional": true
96
+ },
93
97
  "@deepseek-ai/dsh-skill": {
94
98
  "optional": true
95
99
  },