@modusensus/dsh-mneme 0.8.11 → 0.8.13
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/README.md +5 -5
- package/assets/icon.svg +3 -3
- package/lib/api.js +2 -0
- package/lib/client.js +12 -1
- package/lib/config.js +66 -1
- package/lib/dream/decisions.js +16 -0
- package/lib/dream/sleep.js +3 -0
- package/lib/dream.js +57 -9
- package/lib/entities/extractor.js +4 -1
- package/lib/graph/anchoring.js +180 -0
- package/lib/guide.js +13 -1
- package/lib/index.js +18 -5
- package/lib/inject.js +15 -3
- package/lib/lang.js +10 -0
- package/lib/sensitive-scan.js +209 -0
- package/lib/service.js +203 -35
- package/lib/settings.js +29 -3
- package/lib/store.js +155 -11
- package/lib/summarize.js +104 -7
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +114 -113
- package/scripts/sync-test-badge.mjs +9 -7
- package/scripts/test-count-sync.mjs +44 -0
- package/src/api.js +2 -0
- package/src/config.js +66 -1
- package/src/dream/decisions.js +16 -0
- package/src/dream/sleep.js +3 -0
- package/src/dream.js +57 -9
- package/src/entities/extractor.js +4 -1
- package/src/graph/anchoring.js +180 -0
- package/src/guide.js +13 -1
- package/src/index.js +18 -5
- package/src/inject.js +15 -3
- package/src/lang.js +10 -0
- package/src/sensitive-scan.js +209 -0
- package/src/service.js +203 -35
- package/src/settings.js +29 -3
- package/src/store.js +155 -11
- package/src/summarize.js +104 -7
- package/test/api.test.js +15 -2
- package/test/baseline-persist.test.js +248 -0
- package/test/dream.test.js +75 -0
- package/test/graph-anchoring.test.js +185 -0
- package/test/graph-inject-budget.test.js +104 -0
- package/test/graph-passive-confirm.test.js +92 -0
- package/test/graph-weight-evolution.test.js +105 -0
- package/test/inject-pools.test.js +19 -0
- package/test/scope-inject-soft.test.js +106 -0
- package/test/scope-strict.test.js +112 -0
- package/test/sensitive-scan.test.js +207 -0
- package/test/summarize.test.js +177 -0
- package/test/test-count-sync.test.js +60 -0
- package/test/write-admission.test.js +115 -0
package/package.json
CHANGED
|
@@ -1,113 +1,114 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@modusensus/dsh-mneme",
|
|
3
|
-
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 9 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
-
"version": "0.8.
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/slow-stack/mneme.git"
|
|
9
|
-
},
|
|
10
|
-
"homepage": "https://github.com/slow-stack/mneme#readme",
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/slow-stack/mneme/issues"
|
|
13
|
-
},
|
|
14
|
-
"type": "module",
|
|
15
|
-
"main": "lib/index.js",
|
|
16
|
-
"icon": "assets/icon.svg",
|
|
17
|
-
"bin": {
|
|
18
|
-
"dsh-mneme": "bin/cli.mjs",
|
|
19
|
-
"dsh-mneme-mcp": "bin/dsh-mneme-mcp.mjs"
|
|
20
|
-
},
|
|
21
|
-
"exports": {
|
|
22
|
-
".": {
|
|
23
|
-
"default": "./lib/index.js"
|
|
24
|
-
},
|
|
25
|
-
"./client": {
|
|
26
|
-
"default": "./lib/client.js"
|
|
27
|
-
},
|
|
28
|
-
"./package.json": "./package.json",
|
|
29
|
-
"./locale/*": "./locale/*"
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"assets",
|
|
33
|
-
"bin",
|
|
34
|
-
"lib",
|
|
35
|
-
"locale",
|
|
36
|
-
"src",
|
|
37
|
-
"scripts",
|
|
38
|
-
"test",
|
|
39
|
-
"runtime-manifest.json",
|
|
40
|
-
"cordis.patch.yml"
|
|
41
|
-
],
|
|
42
|
-
"dsh": {
|
|
43
|
-
"client": {
|
|
44
|
-
"inject": [
|
|
45
|
-
"slots",
|
|
46
|
-
"locale"
|
|
47
|
-
],
|
|
48
|
-
"platform": "web"
|
|
49
|
-
},
|
|
50
|
-
"bundle": {
|
|
51
|
-
"patch": "./cordis.patch.yml"
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
"peerDependencies": {
|
|
55
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
57
|
-
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
58
|
-
"@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
59
|
-
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
60
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
61
|
-
"@huggingface/transformers": ">=4.2.0 <5",
|
|
62
|
-
"dsh-better-sidebar": "^0.18.0"
|
|
63
|
-
},
|
|
64
|
-
"peerDependenciesMeta": {
|
|
65
|
-
"dsh-better-sidebar": {
|
|
66
|
-
"optional": true
|
|
67
|
-
},
|
|
68
|
-
"@huggingface/transformers": {
|
|
69
|
-
"optional": true
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
"devDependencies": {
|
|
73
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
74
|
-
"@deepseek-ai/dsh-host-webserver": "^0.
|
|
75
|
-
"@deepseek-ai/dsh-llm": "^0.
|
|
76
|
-
"@deepseek-ai/dsh-system-prompt": "^0.
|
|
77
|
-
"@deepseek-ai/dsh-tools": "^0.
|
|
78
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
79
|
-
"@huggingface/transformers": "^4.3.0",
|
|
80
|
-
"c8": "^12.0.0",
|
|
81
|
-
"lucide": "^1.42.0"
|
|
82
|
-
},
|
|
83
|
-
"scripts": {
|
|
84
|
-
"sync": "node scripts/sync-lib.js",
|
|
85
|
-
"prepack": "npm run sync",
|
|
86
|
-
"test": "node --test test/*.test.js",
|
|
87
|
-
"test:coverage": "c8 node --test test/*.test.js",
|
|
88
|
-
"badge:sync": "node scripts/sync-test-badge.mjs",
|
|
89
|
-
"e2e": "node scripts/e2e-dsh.js",
|
|
90
|
-
"stress": "node scripts/stress-dsh.js"
|
|
91
|
-
},
|
|
92
|
-
"c8": {
|
|
93
|
-
"reporter": [
|
|
94
|
-
"text",
|
|
95
|
-
"lcov"
|
|
96
|
-
],
|
|
97
|
-
"exclude": [
|
|
98
|
-
"lib/**",
|
|
99
|
-
"test/**",
|
|
100
|
-
"**/*.test.js"
|
|
101
|
-
]
|
|
102
|
-
},
|
|
103
|
-
"dependencies": {
|
|
104
|
-
"morphicons": "^1.7.1"
|
|
105
|
-
},
|
|
106
|
-
"overrides": {
|
|
107
|
-
"adm-zip": "0.6.1"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@modusensus/dsh-mneme",
|
|
3
|
+
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 9 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
+
"version": "0.8.13",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/slow-stack/mneme.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/slow-stack/mneme#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/slow-stack/mneme/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "lib/index.js",
|
|
16
|
+
"icon": "assets/icon.svg",
|
|
17
|
+
"bin": {
|
|
18
|
+
"dsh-mneme": "bin/cli.mjs",
|
|
19
|
+
"dsh-mneme-mcp": "bin/dsh-mneme-mcp.mjs"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"default": "./lib/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"default": "./lib/client.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
"./locale/*": "./locale/*"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"assets",
|
|
33
|
+
"bin",
|
|
34
|
+
"lib",
|
|
35
|
+
"locale",
|
|
36
|
+
"src",
|
|
37
|
+
"scripts",
|
|
38
|
+
"test",
|
|
39
|
+
"runtime-manifest.json",
|
|
40
|
+
"cordis.patch.yml"
|
|
41
|
+
],
|
|
42
|
+
"dsh": {
|
|
43
|
+
"client": {
|
|
44
|
+
"inject": [
|
|
45
|
+
"slots",
|
|
46
|
+
"locale"
|
|
47
|
+
],
|
|
48
|
+
"platform": "web"
|
|
49
|
+
},
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
57
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
59
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
60
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
61
|
+
"@huggingface/transformers": ">=4.2.0 <5",
|
|
62
|
+
"dsh-better-sidebar": "^0.18.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"dsh-better-sidebar": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@huggingface/transformers": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
74
|
+
"@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.1",
|
|
78
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
79
|
+
"@huggingface/transformers": "^4.3.0",
|
|
80
|
+
"c8": "^12.0.0",
|
|
81
|
+
"lucide": "^1.42.0"
|
|
82
|
+
},
|
|
83
|
+
"scripts": {
|
|
84
|
+
"sync": "node scripts/sync-lib.js",
|
|
85
|
+
"prepack": "npm run sync",
|
|
86
|
+
"test": "node --test test/*.test.js",
|
|
87
|
+
"test:coverage": "c8 node --test test/*.test.js",
|
|
88
|
+
"badge:sync": "node scripts/sync-test-badge.mjs",
|
|
89
|
+
"e2e": "node scripts/e2e-dsh.js",
|
|
90
|
+
"stress": "node scripts/stress-dsh.js"
|
|
91
|
+
},
|
|
92
|
+
"c8": {
|
|
93
|
+
"reporter": [
|
|
94
|
+
"text",
|
|
95
|
+
"lcov"
|
|
96
|
+
],
|
|
97
|
+
"exclude": [
|
|
98
|
+
"lib/**",
|
|
99
|
+
"test/**",
|
|
100
|
+
"**/*.test.js"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"dependencies": {
|
|
104
|
+
"morphicons": "^1.7.1"
|
|
105
|
+
},
|
|
106
|
+
"overrides": {
|
|
107
|
+
"adm-zip": "0.6.1",
|
|
108
|
+
"brace-expansion": "5.0.12"
|
|
109
|
+
},
|
|
110
|
+
"allowScripts": {
|
|
111
|
+
"onnxruntime-node@1.24.3": true,
|
|
112
|
+
"sharp@0.34.5": true
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
// 1. 跑一遍全量测试(npm test),取套件总数(`ℹ tests N` 汇总行);
|
|
7
7
|
// 2. 把双 README 的 tests 徽章(img.shields.io/badge/tests-N%20passed)与
|
|
8
8
|
// 开发命令注释(# N 个测试 / # 运行 N 个测试 / # N 个 node:test 测试 /
|
|
9
|
-
// # N tests
|
|
9
|
+
// # N tests)刷成该数字——形状集合收在 scripts/test-count-sync.mjs,与发版
|
|
10
|
+
// 自动路径 scripts/release-prep.mjs 共用(两条路径各存一份曾导致 v0.8.11
|
|
11
|
+
// 徽章与注释不同数);
|
|
10
12
|
// 3. 有变化就写回文件(由调用方决定是否 commit/push),无变化静默退出。
|
|
11
13
|
//
|
|
12
14
|
// 测试失败(fail>0 或进程非零退出)时以非零退出且不改任何文件——徽章永远
|
|
@@ -16,6 +18,7 @@ import { execSync } from "node:child_process";
|
|
|
16
18
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
17
19
|
import { dirname, join } from "node:path";
|
|
18
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { applyTestCount } from "./test-count-sync.mjs";
|
|
19
22
|
|
|
20
23
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
21
24
|
const repoRoot = join(scriptDir, "..", "..");
|
|
@@ -46,18 +49,17 @@ if (!count) {
|
|
|
46
49
|
process.exit(1);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
// 3) 刷双 README(徽章 +
|
|
52
|
+
// 3) 刷双 README(徽章 + 四种开发命令注释形状)
|
|
53
|
+
// 形状集合与 release-prep.mjs(发版自动路径)共用,收在 scripts/test-count-sync.mjs —
|
|
54
|
+
// 两条路径各自维护一份规则正是 v0.8.11 注释漂移的根因。
|
|
50
55
|
const targets = [
|
|
51
56
|
join(repoRoot, "README.md"),
|
|
52
57
|
join(repoRoot, "dsh-mneme", "README.md")
|
|
53
58
|
];
|
|
54
59
|
let changed = 0;
|
|
55
60
|
for (const file of targets) {
|
|
56
|
-
|
|
57
|
-
const next = text
|
|
58
|
-
.replace(/tests-\d+%20passed-/g, `tests-${count}%20passed-`)
|
|
59
|
-
.replace(/(#\s*(?:运行\s*)?)\d+( 个(?:\s*node:test)?\s*测试)/g, `$1${count}$2`)
|
|
60
|
-
.replace(/(#\s*)\d+( tests)/g, `$1${count}$2`);
|
|
61
|
+
const text = readFileSync(file, "utf8");
|
|
62
|
+
const next = applyTestCount(text, count);
|
|
61
63
|
if (next !== text) {
|
|
62
64
|
writeFileSync(file, next);
|
|
63
65
|
changed += 1;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// 测试数在双 README 里以多种形状重复出现:shields.io 徽章 URL,以及四种 `# N …测试`
|
|
2
|
+
// 注释(`# N 个测试` / `# 运行 N 个测试` / `# N 个 node:test 测试` / `# N tests`)。
|
|
3
|
+
// 所有出现处必须永远报同一个数。
|
|
4
|
+
//
|
|
5
|
+
// 这些规则原先在两条写入路径里各存一份,且形状集合不同:
|
|
6
|
+
// - scripts/release-prep.mjs(Release Prep 工作流,自动,每次发版跑)
|
|
7
|
+
// - dsh-mneme/scripts/sync-test-badge.mjs(npm run badge:sync,手动)
|
|
8
|
+
// release-prep 当时只 replace 徽章 URL,于是 v0.8.11 发版时徽章自动跳到 1437,
|
|
9
|
+
// 四条注释留在 1431 —— 自动路径反而成了漂移的来源。规则收在这里共用,
|
|
10
|
+
// 由 test/test-count-sync.test.js 锁住形状集合与「双 README 同数」这条不变量。
|
|
11
|
+
//
|
|
12
|
+
// 徽章正则刻意不带尾随 `-`:`tests-N%20passed` 既能命中 `…passed-3E63DD`(root)
|
|
13
|
+
// 也能命中 `…passed-success`(包 README),将来出现无后缀写法也不会静默漏改。
|
|
14
|
+
|
|
15
|
+
const BADGE = /tests-\d+%20passed/g;
|
|
16
|
+
const COMMENT_CJK = /(#\s*(?:运行\s*)?)\d+( 个(?:\s*node:test)?\s*测试)/g;
|
|
17
|
+
const COMMENT_EN = /(#\s*)\d+( tests)/g;
|
|
18
|
+
|
|
19
|
+
/** 把一段文本里所有测试数出现处刷成 count(含徽章与四种注释形状)。 */
|
|
20
|
+
export function applyTestCount(text, count) {
|
|
21
|
+
return text
|
|
22
|
+
.replace(BADGE, `tests-${count}%20passed`)
|
|
23
|
+
.replace(COMMENT_CJK, `$1${count}$2`)
|
|
24
|
+
.replace(COMMENT_EN, `$1${count}$2`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 只读地抽出文本里所有测试数字面量及其形状,供「处处一致」断言使用。
|
|
29
|
+
* 历史版本表里的「745 测试全绿」不带 `#` 前缀,刻意不在两种口径内——那是各版本
|
|
30
|
+
* 当时的真实记录,不该被后续发版改写。
|
|
31
|
+
*/
|
|
32
|
+
export function readTestCounts(text) {
|
|
33
|
+
const found = [];
|
|
34
|
+
for (const m of text.matchAll(/tests-(\d+)%20passed/g)) {
|
|
35
|
+
found.push({ shape: "badge", value: Number(m[1]) });
|
|
36
|
+
}
|
|
37
|
+
for (const m of text.matchAll(/#\s*(?:运行\s*)?(\d+)\s*个(?:\s*node:test)?\s*测试/g)) {
|
|
38
|
+
found.push({ shape: "comment-cjk", value: Number(m[1]) });
|
|
39
|
+
}
|
|
40
|
+
for (const m of text.matchAll(/#\s*(\d+)\s+tests/g)) {
|
|
41
|
+
found.push({ shape: "comment-en", value: Number(m[1]) });
|
|
42
|
+
}
|
|
43
|
+
return found;
|
|
44
|
+
}
|
package/src/api.js
CHANGED
|
@@ -143,6 +143,8 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
143
143
|
"llmAudit.enabled": ["llmAudit", "enabled"],
|
|
144
144
|
"writeAdmission.enabled": ["writeAdmission", "enabled"],
|
|
145
145
|
"writeAdmission.enforce": ["writeAdmission", "enforce"]
|
|
146
|
+
// sensitiveScanEnabled(#164 A2)是顶层扁平键,不需要进这张表——走 configFlagValue
|
|
147
|
+
// 的默认分支即可。
|
|
146
148
|
};
|
|
147
149
|
function configFlagValue(key) {
|
|
148
150
|
const path = NESTED_FLAG_PATHS[key];
|
package/src/config.js
CHANGED
|
@@ -281,6 +281,14 @@ export const Config = z.object({
|
|
|
281
281
|
// 显式开启后放宽跨类型合并检查(类型边界由用户自行承担);配合
|
|
282
282
|
// dreamSkipInvalid 理解:关闭 skipInvalid 时跨类型 merge 直接整单拒绝。
|
|
283
283
|
allowCrossTypeMerge: z.boolean().default(false),
|
|
284
|
+
// issue #339 / E8 考卷:merge 护栏(opt-in,默认关 = 现状)。开启后合并对象
|
|
285
|
+
// 命中长保留类型(preference/pattern/rejected_solution/constraint/pitfall,
|
|
286
|
+
// 与 archive 护栏同一张表)的 merge 决策整条跳过——E8 实测巩固损耗里
|
|
287
|
+
// 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉,而 archive 护栏只挡
|
|
288
|
+
// archive 不挡 merge,「更精炼的摘要」恰是约束失真的主通道。skipInvalid
|
|
289
|
+
// (默认开)时被跳条目进 dream_runs.skipped、run 记 degraded;关闭
|
|
290
|
+
// skipInvalid 时整单拒绝。与 allowCrossTypeMerge 同用时长 guard 先判。
|
|
291
|
+
dreamMergeGuard: z.boolean().default(false),
|
|
284
292
|
// Rule version for dream adjudication: when this bumps, older dream_runs
|
|
285
293
|
// degrade to historical evidence (their receipts no longer drive live
|
|
286
294
|
// decisions). Default 0 = no versioning in use yet.
|
|
@@ -463,6 +471,35 @@ export const Config = z.object({
|
|
|
463
471
|
// 检索融合池(与 BM25 同级的确认/回填信号)。默认关=检索行为与 #219 前
|
|
464
472
|
// 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
|
|
465
473
|
entityRecallEnabled: z.boolean().default(false),
|
|
474
|
+
// 边权重演化(issue #24 块2):touch 门控是否把被寻回路径的边权重抬升。
|
|
475
|
+
// 默认关=边初值纯由建边来源决定(store.RELATION_SOURCE_DEFAULTS),演化
|
|
476
|
+
// 不生效;开=被激活的边经 bumpRelationWeight 上涨(只加不减、封顶 1.0)。
|
|
477
|
+
graphWeightEnabled: z.boolean().default(false),
|
|
478
|
+
// 单次有效触达的抬升幅度。
|
|
479
|
+
graphWeightDelta: z.number().min(0).max(1).default(0.1),
|
|
480
|
+
// 被动确认(issue #24 块4):heat 触达(记忆被召回/注入=被用)即对该记忆
|
|
481
|
+
// 挂联的关系边做被动确认——bumpRelationWeight 抬一格。默认关=零行为变化;
|
|
482
|
+
// 开=正常触达演化边权,「关系是用出来的」闭环落地(异常路径复核留后续块)。
|
|
483
|
+
graphPassiveConfirm: z.boolean().default(false),
|
|
484
|
+
|
|
485
|
+
// --- 图谱锚定层(issue #24 · 块1 Activation Anchoring)-------------------
|
|
486
|
+
// entityRecallEnabled 之上再开一层:以查询命中的实体为种子,沿实体关系表
|
|
487
|
+
// 级联扩散(默认深度 2),把「邻居实体挂联的记忆」也带进融合池。种子集
|
|
488
|
+
// 上限 graphSeedCap、级联深度 graphCascadeDepth 一并下发到 anchorSeeds /
|
|
489
|
+
// cascadeDepths(见 src/graph/anchoring.js)。默认关=行为与 #219 单跳轴
|
|
490
|
+
// 逐字节一致;走 feature_flags 面板可启停,lightMode 强制关闭。
|
|
491
|
+
graphAnchoringEnabled: z.boolean().default(false),
|
|
492
|
+
graphSeedCap: z.natural().min(1).max(30).default(12),
|
|
493
|
+
graphCascadeDepth: z.natural().min(1).max(3).default(2),
|
|
494
|
+
|
|
495
|
+
// 关联提示防幻觉(issue #24 块3):图召回的候选(查询命中实体的挂联记忆)
|
|
496
|
+
// 进注入时默认只参与排序、不改变注入块构成(graphInjectHint=false 保守档);
|
|
497
|
+
// 开启后注入文本里以固定句式「[检索线索] …」标出,引导语说明这是链路信息
|
|
498
|
+
// 非事实断言,防 LLM 单次消费把桥接召回当有依据的事实回头扩散。
|
|
499
|
+
graphInjectHint: z.boolean().default(false),
|
|
500
|
+
// 图召回候选在注入块里的独立预算(条数上限)。与 pin/document 预算同构:
|
|
501
|
+
// 不占 maxInjectedItems 名额、只约束「桥接线索」这类低信任信息的常驻占比。
|
|
502
|
+
graphInjectBudget: z.natural().min(0).max(5).default(1),
|
|
466
503
|
|
|
467
504
|
// --- sleep mode: idle-triggered deep maintenance (v0.4.0) ---------------
|
|
468
505
|
// Opt-in, off by default. Unlike autoDream (threshold-triggered, lightweight)
|
|
@@ -614,6 +651,25 @@ export const Config = z.object({
|
|
|
614
651
|
enforce: z.boolean().default(false)
|
|
615
652
|
}).default({}),
|
|
616
653
|
|
|
654
|
+
// --- #164 A2: secret / PII scan at the write boundary ----------------------
|
|
655
|
+
// 写入边界的密钥 / PII 判据(src/sensitive-scan.js)自身的闸。与 writeAdmission
|
|
656
|
+
// 的 enabled 分开是有意的:那一个管 #254 第 1 级的空白 / 噪声判据,本键管 A2 这
|
|
657
|
+
// 一类判据,两批的误杀面差一个量级(空白 / 噪声没有解释空间,邮箱 / 手机号有),
|
|
658
|
+
// 绑在同一个开关上就没法单独观察 A2 的命中分布。
|
|
659
|
+
//
|
|
660
|
+
// 分层必须写清:判据的唯一调用点在 #254 第 1 级的闸门里(write-admission.js 的
|
|
661
|
+
// firstLevelHit),闸门不走第 1 级就没人来调这个扫描器。所以本键是「闸门内这一批
|
|
662
|
+
// 判据参不参与」,不是一条能独立跑的链路——单开本键就是零行为变化:
|
|
663
|
+
// writeAdmission.enabled 关 → 第 1 级整个不跑,本键开也没用
|
|
664
|
+
// enabled 开 + 本键关 → 只跑空白 / 噪声那一批
|
|
665
|
+
// enabled 开 + 本键开 + enforce 关 → 命中留审计,写入照常(观察档)
|
|
666
|
+
// enabled 开 + 本键开 + enforce 开 → 命中即拒绝
|
|
667
|
+
// 默认关 = 只计量那一阶段的行为逐字节保留(#332 合并时 sensitiveScan 就是 null)。
|
|
668
|
+
// #164 的「默认仅告警、拦截 opt-in」落在 enforce 上:命中落一条审计
|
|
669
|
+
// (metadata.deny.kind)但照常写入,真要拦得 enabled 与 enforce 同时开。
|
|
670
|
+
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
671
|
+
sensitiveScanEnabled: z.boolean().default(false),
|
|
672
|
+
|
|
617
673
|
// --- recall evaluation: test-result storage (v0.4.6, 方案 B) --------------
|
|
618
674
|
// Separate retrieval evaluation snapshots from the production recall audit.
|
|
619
675
|
// When false (default) evaluateRetrieval still computes precision/recall/mrr
|
|
@@ -685,7 +741,8 @@ export const Config = z.object({
|
|
|
685
741
|
// agent_scope, workspace_scope, sensitivity)。检索侧加权/过滤在 A2/A3 落地。
|
|
686
742
|
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
687
743
|
scopeEnabled: z.boolean().default(false),
|
|
688
|
-
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope
|
|
744
|
+
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见,
|
|
745
|
+
// 检索与注入排序同权重,issue #339 补齐注入通道);
|
|
689
746
|
// 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
|
|
690
747
|
// 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
|
|
691
748
|
// 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
|
|
@@ -712,6 +769,14 @@ const LIGHT_MODE_OFF = [
|
|
|
712
769
|
"bm25SearchEnabled",
|
|
713
770
|
// 轻量模式不开图召回轴(#219,依赖实体抽取产出;抽取本身已被关掉)。
|
|
714
771
|
"entityRecallEnabled",
|
|
772
|
+
// 轻量模式不开图谱锚定层级联(#24 块1,依赖实体抽取 + 关系表,抽取已关则无边可扩)。
|
|
773
|
+
"graphAnchoringEnabled",
|
|
774
|
+
// 轻量模式不开边权重演化(#24 块2,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
|
|
775
|
+
"graphWeightEnabled",
|
|
776
|
+
// 轻量模式不开关联提示标注(#24 块3,常驻注入文本多一段提示成本)。
|
|
777
|
+
"graphInjectHint",
|
|
778
|
+
// 轻量模式不开被动确认(#24 块4,依赖实体抽取产出;抽取关则无 v08 边可抬升)。
|
|
779
|
+
"graphPassiveConfirm",
|
|
715
780
|
// 轻量模式不开叙述条(额外 LLM 调用;#164 对齐,opt-in)。
|
|
716
781
|
"dreamNarrativeEnabled",
|
|
717
782
|
// 轻量模式不开 document 指针行(#230,opt-in:注册/注入/检索增强全随闸)。
|
package/src/dream/decisions.js
CHANGED
|
@@ -210,6 +210,22 @@ export function validateDecisions(decisions, snapshot, options = {}) {
|
|
|
210
210
|
if (mergeTypes.size > 1 && options.allowCrossTypeMerge !== true) {
|
|
211
211
|
local.push(`${at}: merge ids span multiple types (${[...mergeTypes].join(", ")})`);
|
|
212
212
|
}
|
|
213
|
+
// issue #339 / E8 考卷:merge 护栏(opt-in,dreamMergeGuard)。巩固损耗
|
|
214
|
+
// 实测里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉——archive
|
|
215
|
+
// 护栏(上方)只挡 archive 不挡 merge,而 merge 的「更精炼摘要」恰是
|
|
216
|
+
// 约束失真的主通道。开启后被合并对象命中 ARCHIVE_GUARDED_TYPES 的
|
|
217
|
+
// merge 决策整条跳过(与 archive 护栏同款通道:skipInvalid 时 skipped
|
|
218
|
+
// 明细、严格模式整单拒绝)。
|
|
219
|
+
if (local.length === 0 && options.mergeGuard === true) {
|
|
220
|
+
const guardedTypes = new Set();
|
|
221
|
+
for (const id of d.ids) {
|
|
222
|
+
const mem = snapshot.get(id);
|
|
223
|
+
if (mem && ARCHIVE_GUARDED_TYPES.has(mem.type)) guardedTypes.add(mem.type);
|
|
224
|
+
}
|
|
225
|
+
if (guardedTypes.size > 0) {
|
|
226
|
+
local.push(`${at}: merge of long-retention type(s) ${[...guardedTypes].join(", ")} is blocked by dreamMergeGuard`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
213
229
|
}
|
|
214
230
|
}
|
|
215
231
|
if (local.length > 0) {
|
package/src/dream/sleep.js
CHANGED
|
@@ -373,6 +373,9 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
373
373
|
minAgeHours: config.reflectionUpdateMinAgeHours,
|
|
374
374
|
skipInvalid: config.dreamSkipInvalid !== false,
|
|
375
375
|
allowCrossTypeMerge: config.allowCrossTypeMerge === true,
|
|
376
|
+
// issue #339 / E8:guarded 类型 merge 护栏(opt-in,dreamMergeGuard),
|
|
377
|
+
// 与 dream 主链路同一判据。
|
|
378
|
+
mergeGuard: config.dreamMergeGuard === true,
|
|
376
379
|
// Issue #126 review(Copilot):默认档必须真的只放行旧动作集——只换 prompt 挡不住
|
|
377
380
|
// 模型自发输出 supersede / differentiate,那样"opt-in 零行为变化"就不成立。
|
|
378
381
|
allowedActions: actionSet === "full" ? null : LEGACY_ACTIONS
|
package/src/dream.js
CHANGED
|
@@ -52,6 +52,26 @@ function totalChars(memories) {
|
|
|
52
52
|
return memories.reduce((sum, m) => sum + (m.title?.length ?? 0) + (m.content?.length ?? 0), 0);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// 阈值口径的唯一实现:活跃记忆(未归档、非 summary/document)的条数与正文字符数。
|
|
56
|
+
// 触发判定(shouldTrigger)与「成功轮把基线落库」(runDream 的 finish)都走这里——
|
|
57
|
+
// 两处若各写一份过滤条件,重启播种读到的基线就会和运行期判定口径漂移(一个把
|
|
58
|
+
// document 算进去、一个不算),阈值随之失真,且这种漂移只会在重启后才显形。
|
|
59
|
+
function activeStoreSize(service) {
|
|
60
|
+
const memories = service.all().filter((m) => !m.archived && m.type !== "summary" && m.type !== "document");
|
|
61
|
+
return { count: memories.length, chars: totalChars(memories) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 基线的唯一合法形状:两个非负整数;其余(缺字段、负数、非整数、整个对象缺失)
|
|
65
|
+
// 一律视为「没有基线」→ 调用方退回 {0,0}。播种值来自持久化列,可能是老库的 NULL
|
|
66
|
+
// 或手工改坏的库,判定必须比调用点更严格,而不是相信数据库里存的一定合法。
|
|
67
|
+
function toBaseline(raw) {
|
|
68
|
+
if (!raw || typeof raw !== "object") return null;
|
|
69
|
+
const count = Number.isInteger(raw.count) && raw.count >= 0 ? raw.count : null;
|
|
70
|
+
const chars = Number.isInteger(raw.chars) && raw.chars >= 0 ? raw.chars : null;
|
|
71
|
+
if (count === null || chars === null) return null;
|
|
72
|
+
return { count, chars };
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
// ---------------------------------------------------------------- audit
|
|
56
76
|
|
|
57
77
|
/**
|
|
@@ -760,7 +780,7 @@ export async function maintainIndexAfterDream(decisions, service, semantic) {
|
|
|
760
780
|
// 用户配得比这更大的 dreamMinIntervalMinutes 基数压小(见 effectiveMinIntervalMs)。
|
|
761
781
|
const FAILURE_BACKOFF_CAP_MS = 30 * 60 * 1000;
|
|
762
782
|
|
|
763
|
-
export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChars = 5000, delayMs = 2000, minIntervalMs = 0, failureBackoff = false, logger, semantic = null, lastRunAtSeed = 0, peakHours = "", peakMaxDeferMinutes = 120, auditPeakSkip = null, now = () => Date.now(), setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout }) {
|
|
783
|
+
export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChars = 5000, delayMs = 2000, minIntervalMs = 0, failureBackoff = false, logger, semantic = null, lastRunAtSeed = 0, baselineSeed = null, peakHours = "", peakMaxDeferMinutes = 120, auditPeakSkip = null, now = () => Date.now(), setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout }) {
|
|
764
784
|
let pendingTimer = null;
|
|
765
785
|
// Issue #239(第 4 项)镜像到巩固:高峰顺延定时器。与 pendingTimer 分开——两者
|
|
766
786
|
// 语义不同(一个是「马上要跑」,一个是「等出高峰再跑」),合成一个变量会让
|
|
@@ -768,7 +788,13 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
768
788
|
let deferTimer = null;
|
|
769
789
|
let running = false;
|
|
770
790
|
let disposed = false;
|
|
771
|
-
|
|
791
|
+
// Issue #89(基线半边):阈值基线跨重启持久化。baselineSeed 由调用方从
|
|
792
|
+
// dream_runs 读上次成功轮结束时的库规模(store.lastDreamBaseline)——它只活在
|
|
793
|
+
// 闭包内存里时进程重启即归零,而库里记忆远多于阈值,重启后第一条写入就满足
|
|
794
|
+
// overBase、绕过阈值开跑整轮(#291 只补了 lastRunAt 那半边)。缺种子(新库、
|
|
795
|
+
// 升级后还没跑过成功轮)或种子非法时退回 {0,0}:不假装知道基线,行为与升级前
|
|
796
|
+
// 逐字节一致——修的是「知道却不认」,不是凭空造一个基线。
|
|
797
|
+
let baseline = toBaseline(baselineSeed) ?? { count: 0, chars: 0 };
|
|
772
798
|
let inFlight = null;
|
|
773
799
|
// Issue #89(请求 2):上一次实际开跑时刻。失败/degraded 的 run 也占用
|
|
774
800
|
// 最小间隔——节流的目的正是防止失败调用连发;间隔内的触发静默跳过。
|
|
@@ -792,9 +818,7 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
792
818
|
}
|
|
793
819
|
|
|
794
820
|
function shouldTrigger(service) {
|
|
795
|
-
const
|
|
796
|
-
const count = memories.length;
|
|
797
|
-
const chars = totalChars(memories);
|
|
821
|
+
const { count, chars } = activeStoreSize(service);
|
|
798
822
|
const overBase = count >= baseline.count + thresholdCount || chars >= baseline.chars + thresholdChars;
|
|
799
823
|
const overAbs = count >= thresholdCount || chars >= thresholdChars;
|
|
800
824
|
return { trigger: overAbs && overBase, count, chars };
|
|
@@ -881,7 +905,10 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
881
905
|
if (result && result.ok) {
|
|
882
906
|
consecutiveFailures = 0; // Issue #292:成功清零,下次触发回到基数间隔
|
|
883
907
|
try {
|
|
884
|
-
|
|
908
|
+
// Issue #89(基线半边):run 自报的基线(与审计行同一份数字)优先——重启
|
|
909
|
+
// 播种读的就是它,两条路径共用唯一口径;no-op 桩不回这个字段时退回现场
|
|
910
|
+
// 计算,最小测试替身的行为逐字节不变。
|
|
911
|
+
baseline = toBaseline(result.baseline) ?? shouldTrigger(service);
|
|
885
912
|
} catch (error) {
|
|
886
913
|
// Store closed mid-flight: keep the last known baseline.
|
|
887
914
|
logger?.warn?.(`dsh-mneme dream: baseline refresh failed: ${String(error)}`);
|
|
@@ -986,6 +1013,19 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
986
1013
|
const applied = result.applied ?? 0;
|
|
987
1014
|
const summaryStored = result.summary ?? false;
|
|
988
1015
|
const receipt = buildReceipt({ runId, status, snapshotHash, inputCount: snapshot.size, applied, summaryStored });
|
|
1016
|
+
// Issue #89(基线半边):成功轮把「本轮结束后的库规模」落进审计行 = 阈值基线的
|
|
1017
|
+
// 持久化副本,重启后由 store.lastDreamBaseline 播种。判定条件必须与调度器刷新
|
|
1018
|
+
// 基线的条件严格一致(result.ok)——非 ok 轮不推进基线是既有语义,若这里也落值,
|
|
1019
|
+
// 重启后播种就会拿到一个「其实没推进过」的假基线,阈值反而被抬高。
|
|
1020
|
+
// 取不到(库已关闭等)就落 NULL:宁可不持久化,也不写一个错的基线。
|
|
1021
|
+
let baselineSnapshot = null;
|
|
1022
|
+
if (result.ok) {
|
|
1023
|
+
try {
|
|
1024
|
+
baselineSnapshot = activeStoreSize(service);
|
|
1025
|
+
} catch {
|
|
1026
|
+
baselineSnapshot = null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
989
1029
|
try {
|
|
990
1030
|
service.saveDreamRun({
|
|
991
1031
|
id: runId,
|
|
@@ -1023,12 +1063,18 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
1023
1063
|
// 而非内联进 decisions——degraded 的 decisions 是合法子集,下游按「决策
|
|
1024
1064
|
// 数组」消费,内联标记会污染其它读者(失败轮的 _validationFailed 是整单
|
|
1025
1065
|
// 拒绝哨兵,语义与「部分应用」不同,也不复用)。
|
|
1026
|
-
skipped: result.skipped
|
|
1066
|
+
skipped: result.skipped,
|
|
1067
|
+
// Issue #89(基线半边):成功的 run 才带这两个值(见上方 baselineSnapshot
|
|
1068
|
+
// 的注释)。两列要么同时有值、要么同时为 NULL,读侧以「同时有值」认账。
|
|
1069
|
+
store_count: baselineSnapshot?.count,
|
|
1070
|
+
store_chars: baselineSnapshot?.chars
|
|
1027
1071
|
});
|
|
1028
1072
|
} catch (error) {
|
|
1029
1073
|
logger?.warn?.(`dsh-mneme dream: failed to record audit run: ${String(error)}`);
|
|
1030
1074
|
}
|
|
1031
|
-
|
|
1075
|
+
// Issue #89(基线半边):把「与审计行同一份数字」的基线回给调度器,两条路径
|
|
1076
|
+
// (进程内刷新 / 跨重启播种)因此共享唯一口径;调度器拿不到时会退出现场计算。
|
|
1077
|
+
return { ...result, runId, receipt, snapshotHash, baseline: baselineSnapshot ?? undefined };
|
|
1032
1078
|
};
|
|
1033
1079
|
if (!route) {
|
|
1034
1080
|
logger?.warn?.("dsh-mneme dream: no llm route available");
|
|
@@ -1135,7 +1181,9 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
|
|
|
1135
1181
|
// Issue #89:v0.6.9(Issue #26)的宽容路径在 v0.7.11 重写中丢失——单条
|
|
1136
1182
|
// 非法决策重新只跳过该条、合法子集照常应用(run 记为 degraded)。
|
|
1137
1183
|
skipInvalid: config.dreamSkipInvalid !== false,
|
|
1138
|
-
allowCrossTypeMerge: config.allowCrossTypeMerge === true
|
|
1184
|
+
allowCrossTypeMerge: config.allowCrossTypeMerge === true,
|
|
1185
|
+
// issue #339 / E8:guarded 类型 merge 护栏(opt-in,dreamMergeGuard)。
|
|
1186
|
+
mergeGuard: config.dreamMergeGuard === true
|
|
1139
1187
|
});
|
|
1140
1188
|
// Issue #135:模型把 UUID 缩写成前缀时,唯一前缀已在校验前被解析回完整 id。
|
|
1141
1189
|
// 解析量是模型输出质量的一个直接信号(>0 意味着模型在缩写 id),记一条 info
|
|
@@ -226,7 +226,10 @@ export async function extractEntities(memory, { store, config, callLLM, logger }
|
|
|
226
226
|
to_entity: toId,
|
|
227
227
|
relation_type: rel.type,
|
|
228
228
|
memory_id: memory.id,
|
|
229
|
-
metadata: { model: options.model || "default" }
|
|
229
|
+
metadata: { model: options.model || "default" },
|
|
230
|
+
// issue #24 块2:LLM 抽取自动建边 → llm 来源(初值 0.4),区别于
|
|
231
|
+
// 人工/确认的满权边。来源进 metadata 之上的独立列,演化与审计可辨。
|
|
232
|
+
source: "llm"
|
|
230
233
|
});
|
|
231
234
|
savedRelations.push(saved);
|
|
232
235
|
} catch (err) {
|