@nightpainters/dsh-ling 1.5.1 → 1.5.2
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 +6 -4
- package/CHANGELOG.md +65 -0
- package/DESIGN.md +141 -14
- package/README.md +55 -31
- package/lib/audit/access-log.js +135 -13
- package/lib/audit/index.js +43 -1
- package/lib/client.js +902 -57
- package/lib/host/api.js +129 -31
- package/lib/host/genesis.js +27 -5
- package/lib/host/guard.js +1 -1
- package/lib/host/inject.js +49 -12
- package/lib/host/l1.js +11 -4
- package/lib/host/memory.js +263 -20
- package/lib/host/persona.js +38 -7
- package/lib/host/session-line.js +648 -0
- package/lib/host/snapshot.js +48 -7
- package/lib/host/summarizer.js +1 -1
- package/lib/index.js +5 -0
- package/package.json +51 -4
package/lib/host/snapshot.js
CHANGED
|
@@ -8,6 +8,12 @@ import { selectL1, formatL1Section } from './l1.js';
|
|
|
8
8
|
import { currentMode } from './mode.js';
|
|
9
9
|
import { neutralizeMustache } from './util.js';
|
|
10
10
|
|
|
11
|
+
// §4「超量折叠成一行」的那行计数(由 `memory.js` 的 branchLogDigest 在明细之后 push 进 `lines`)
|
|
12
|
+
// —— 它是**汇总**,不是明细。零明细时它撑不起整段(见下方渲染判据);有明细时照旧显示。
|
|
13
|
+
// 判据只用开头:明细行一律以主语(主人/器灵)或动作词开头,不会长成 `…另有 N 笔改动`。
|
|
14
|
+
// 若哪天 memory.js 改了措辞,这里退化成"照旧渲染"(fail-open)—— 只影响占不占地方,不吞明细。
|
|
15
|
+
const BRANCH_LOG_FOLD_LINE = /^…另有 \d+ 笔改动/;
|
|
16
|
+
|
|
11
17
|
export function buildSnapshotText(ctx, memory, settings, sessionId, { keywords = [], now = Date.now(), bumpHits = false } = {}) {
|
|
12
18
|
const s = settings.get();
|
|
13
19
|
const personaEnabled = s.persona?.enabled !== false;
|
|
@@ -44,23 +50,52 @@ export function buildSnapshotText(ctx, memory, settings, sessionId, { keywords =
|
|
|
44
50
|
// "树变了" —— 这是复盘时"互相纠正"能成立的前提(不需要推送:复盘对话密集,
|
|
45
51
|
// 每说一句就注入一次 ⇒ 感知延迟只有一轮,体感即实时)。
|
|
46
52
|
// 只报**没见过的**改动(水位 = branch_log 自增 id),否则每轮重复注入同一批。
|
|
53
|
+
// ── B3(1.5.2):水位从"一条按会话"变成**三条并存**,判定都在 `branchLogDigest` 里 ──
|
|
54
|
+
// · `branch_log_wm.<sessionId>` 本会话已看到哪(照旧,B#9);
|
|
55
|
+
// · `branch_log_wm.announced` **全局**"已示人"边界 ⇒ 新会话不再重播已播过的(B3-1);
|
|
56
|
+
// · `branch_log_wm.retired` **全局**超窗"计数式处置"边界(§2 老改动只计数不展开)。
|
|
57
|
+
// 取数改成 FIFO(水位之上最早的一批),水位才可能停在未示人的内容之前(B3-2)。
|
|
47
58
|
// B#9(2026-09-22 审计):**渲染不消费水位** —— 这里只读、不写。
|
|
48
59
|
// buildSnapshotText 也会被"只重建、未开口"的路径调用(invalidateSession / refreshIdleSnapshot),
|
|
49
60
|
// 在那里推进水位会把主人刚改的东西标记成已读,等到真正注入时反而看不到。
|
|
50
|
-
//
|
|
61
|
+
// 推进由 inject.js 在真正要开口时做(它同时管 `.last` 与 §3 的开关)。
|
|
51
62
|
let logMaxId = 0;
|
|
63
|
+
let logShownMaxId = 0;
|
|
64
|
+
let logRetiredMaxId = 0;
|
|
52
65
|
try {
|
|
53
66
|
if (typeof memory.branchLogDigest === 'function') {
|
|
54
67
|
const wmk = 'branch_log_wm.' + String(sessionId || '');
|
|
55
68
|
const prev = Number(memory.kvGet(wmk) || 0);
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
// `now` 必须透传(B3):时间窗(§2)与水位推进要用**同一个时钟**,否则测试不可复现,
|
|
70
|
+
// 生产上也会出现"按渲染时刻算过期、按投递时刻推进"的错位。
|
|
71
|
+
const d = memory.branchLogDigest({ limit: 5, afterId: prev, now });
|
|
72
|
+
// 全表最大 id:与开关、与是否成文**都无关**。它是 `branch_log_wm.last`(复盘退出判据)
|
|
73
|
+
// 的输入 —— 关掉播报也不能让"欠交代"这笔账停摆(见 inject.advanceLogWatermark 注解)。
|
|
74
|
+
logMaxId = Number(d?.maxId) || 0;
|
|
75
|
+
// 真正示人的边界:只有真渲染了明细行时才非零。旧实现/打桩没有该字段 ⇒ 退回 maxId
|
|
76
|
+
// (兼容 mustache 套里 `{lines, maxId:9}` 那种形状)。
|
|
77
|
+
const shown = Number(d?.shownMaxId);
|
|
78
|
+
logShownMaxId = Number.isFinite(shown) ? Math.max(0, shown) : logMaxId;
|
|
79
|
+
logRetiredMaxId = Number(d?.retiredMaxId) || 0;
|
|
80
|
+
// ── 明细 0 条 ⇒ **整段不渲染**(主人拍板,2026-09-27)──────────────────────────
|
|
81
|
+
// 判据是"有没有**明细**行",不是"`lines` 非空":那行「…另有 N 笔改动」**也装在 `lines` 里**
|
|
82
|
+
// (memory.js:1190 附近在明细之后 push),所以只判 `lines.length` 时"零明细"照样成文 ——
|
|
83
|
+
// 注入面就只剩一行不放内容的计数(主人截图:395 笔改动、一条明细也没有)。
|
|
84
|
+
// 只改**渲染**:三个水位字段照旧原样返回(下方 return),`.last` / `announced` / `retired`
|
|
85
|
+
// 的推进与改前逐值相同 —— 不渲染 ≠ 把那批改动标成已读(它们仍在有效下界 `wm` 之上,
|
|
86
|
+
// 下一轮照样进候选;与 §3 开关"只管渲染、不管交代记账"同一取向)。
|
|
87
|
+
const logLines = Array.isArray(d?.lines) ? d.lines : [];
|
|
88
|
+
const logDetailLines = logLines.filter((x) => !BRANCH_LOG_FOLD_LINE.test(String(x)));
|
|
89
|
+
// §3 独立开关:只管**树改动播报**(总闸仍是 cordis 层的 cfg.injectEnabled)。
|
|
90
|
+
// 关掉时:不成文、也不推进"已示人/已处置"水位(否则关着关着就把一段内容标成已读 ⇒
|
|
91
|
+
// 再打开时那段内容永远看不到)。`.last` 照走 —— 它是复盘退出判据,不是"已示人"判据:
|
|
92
|
+
// **开关只管"渲染",不管"交代记账"**(停推进会卡死复盘模式的退出,回归见 tree 套 9-5)。
|
|
93
|
+
if (s.memory?.branchLogEnabled !== false && logDetailLines.length) {
|
|
58
94
|
// A-03 / D-S03(1.5.1 红蓝对抗):段首此前**写死**「主人刚改动了记忆树」,而 schema v12 起
|
|
59
95
|
// 每一行都已带主语(`branchLogDigest` 按 actor 分流成「主人」/「器灵」)⇒ 器灵经工具
|
|
60
96
|
// 改的树,在它**下一轮的系统提示**里被读成"主人刚改动",与 tools.js 的设计意图正相反。
|
|
61
97
|
// 段首改中性措辞,**主语交给每一行** —— 谁改的由行内说,不由段头替它说。
|
|
62
|
-
parts.push(['【记忆树有改动】', ...
|
|
63
|
-
logMaxId = Number(d.maxId) || 0;
|
|
98
|
+
parts.push(['【记忆树有改动】', ...logLines.map((x) => '- ' + x)].join('\n'));
|
|
64
99
|
}
|
|
65
100
|
}
|
|
66
101
|
} catch { /* 感知失败不影响注入 */ }
|
|
@@ -71,8 +106,14 @@ export function buildSnapshotText(ctx, memory, settings, sessionId, { keywords =
|
|
|
71
106
|
return {
|
|
72
107
|
text,
|
|
73
108
|
mode,
|
|
74
|
-
// B#9:本段文本渲染到了哪条 branch_log(inject.js 在真正注入时据此推进水位)
|
|
109
|
+
// B#9:本段文本渲染到了哪条 branch_log(inject.js 在真正注入时据此推进水位)。
|
|
110
|
+
// B3(1.5.2)拆成三个,别混用:
|
|
111
|
+
// logMaxId 全表最大 id ⇒ `.last`(复盘退出判据)
|
|
112
|
+
// logShownMaxId 真正示人的边界 ⇒ 会话水位 + `announced`
|
|
113
|
+
// logRetiredMaxId 超窗只计数的边界 ⇒ `retired`
|
|
75
114
|
logMaxId,
|
|
115
|
+
logShownMaxId,
|
|
116
|
+
logRetiredMaxId,
|
|
76
117
|
builtAt: new Date(now).toISOString(),
|
|
77
118
|
personaVersion: snapshotVersion(s),
|
|
78
119
|
memoryVersion: memory.kvGet('memory_version') || '0',
|
|
@@ -81,7 +122,7 @@ export function buildSnapshotText(ctx, memory, settings, sessionId, { keywords =
|
|
|
81
122
|
|
|
82
123
|
export function snapshotVersion(s) {
|
|
83
124
|
const p = s?.persona || {};
|
|
84
|
-
return [p.userTitle, p.aiName, p.aiTitle, p.tone, p.toneWork, p.toneLife, p.language, p.extraLore, p.enabled, JSON.stringify(p.hardRules), JSON.stringify(p.bottomLines), p.sealed, s?.mode?.lastMode]
|
|
125
|
+
return [p.userTitle, p.aiName, p.aiTitle, p.tone, p.toneWork, p.toneLife, p.language, p.extraLore, p.duty, p.enabled, JSON.stringify(p.hardRules), JSON.stringify(p.bottomLines), p.sealed, s?.mode?.lastMode]
|
|
85
126
|
.join('|');
|
|
86
127
|
}
|
|
87
128
|
|
package/lib/host/summarizer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-ling host — DSH 会话概述器 v1(轻量)。
|
|
2
2
|
// 把 dsh_turns_raw 中每个顶层会话聚合成 source='dsh' 的 conv_overview:
|
|
3
|
-
// title =
|
|
3
|
+
// title = 首条真人消息经启发式清洗后的要点句(剥问候/称呼前缀 → 无实义则往后取前 6 条真人发言里第一条有实义的 → 句读处收尾、不切半句、上限 58 字;全失败才硬截首句);summary = 首问(≤140 字)+ 轮次;类别/关键词启发式。
|
|
4
4
|
// 增量:每会话水位 kv('sumdsh:<sid>') = 已概述的最大 seq;有新增才重建。
|
|
5
5
|
// 无 LLM 深摘要(v1);原文保留在 dsh_turns_raw,供 M2 概述升级。
|
|
6
6
|
import { categorizeTitle } from './classify.js';
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { SettingsFile } from './host/settings-file.js';
|
|
|
8
8
|
import { MemoryStore } from './host/memory.js';
|
|
9
9
|
import { FreezeGate } from './host/freeze.js';
|
|
10
10
|
import { registerMemorySection } from './host/inject.js';
|
|
11
|
+
import { wireSessionLine } from './host/session-line.js';
|
|
11
12
|
import { wireLifecycle } from './host/lifecycle.js';
|
|
12
13
|
import { registerApi } from './host/api.js';
|
|
13
14
|
import { summarizeDsh } from './host/summarizer.js';
|
|
@@ -56,6 +57,10 @@ export function apply(ctx, config = {}) {
|
|
|
56
57
|
|
|
57
58
|
const disposers = [];
|
|
58
59
|
disposers.push(registerMemorySection(ctx, gate, memory, settings));
|
|
60
|
+
// E0「那一行跟着会话走」:只加一个 agent/pre-step 监听(全库目前没有别处用它)。
|
|
61
|
+
// 形态 A —— 只往 `decision.messages` 追加,**不碰** systemPrompt 的 section()/context(),
|
|
62
|
+
// 也绝不调用带 surfaceOp 的 session.append。关掉开关:settings.memory.sessionProgressLine=false。
|
|
63
|
+
disposers.push(wireSessionLine(ctx, memory, settings, cfg.sessionLine || {}));
|
|
59
64
|
disposers.push(wireLifecycle(ctx, gate, memory, settings, cfg));
|
|
60
65
|
disposers.push(registerApi(ctx, { gate, memory, settings }, cfg));
|
|
61
66
|
// E4 可取证访问日志:只挂两个全局钩子,core 侧补丁留的调用点见 lib/audit/patch-spec.js。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nightpainters/dsh-ling",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
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/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/mustache.test.mjs && node tests/retitle.test.mjs && node tests/rules.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",
|
|
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/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",
|
|
28
28
|
"check": "node tools/check-install.mjs",
|
|
29
29
|
"check:publish": "node tools/check-publish.mjs"
|
|
30
30
|
},
|
|
@@ -51,16 +51,63 @@
|
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=22.5.0"
|
|
53
53
|
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@deepseek-ai/cordis": ">=4.0.4",
|
|
56
|
+
"@deepseek-ai/dsh-agent-default-model": ">=0.1.7-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-api-session-controller": ">=0.1.7-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.7-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-store": ">=0.1.7-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-skill": ">=0.1.7-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.7-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-tools": ">=0.1.7-rc.2"
|
|
67
|
+
},
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"@deepseek-ai/dsh-agent-default-model": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"@deepseek-ai/dsh-api-session-controller": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@deepseek-ai/dsh-client-connection": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@deepseek-ai/dsh-client-store": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"@deepseek-ai/dsh-llm": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"@deepseek-ai/dsh-skill": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"@deepseek-ai/dsh-tools": {
|
|
100
|
+
"optional": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
54
103
|
"dsh": {
|
|
55
104
|
"bundle": {
|
|
56
105
|
"patch": "./cordis.patch.yml"
|
|
57
106
|
},
|
|
58
107
|
"client": {
|
|
59
108
|
"inject": [
|
|
60
|
-
"@deepseek-ai/dsh-client-store",
|
|
61
109
|
"@deepseek-ai/dsh-client-connection",
|
|
62
110
|
"@deepseek-ai/dsh-client-locale",
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-slots",
|
|
64
111
|
"@deepseek-ai/dsh-api-session-controller"
|
|
65
112
|
],
|
|
66
113
|
"platform": "web"
|