dsh-memory-fish 0.0.0-stage → 0.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 +99 -0
- package/DESIGN.md +226 -0
- package/LICENSE +21 -0
- package/README.en.md +121 -0
- package/README.md +169 -2
- package/SECURITY.md +61 -0
- package/client/client.js +201 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +43 -0
- package/index.mjs +101 -0
- package/lib/config.mjs +44 -0
- package/lib/core.mjs +104 -0
- package/lib/dream.mjs +162 -0
- package/lib/inject.mjs +83 -0
- package/lib/lock.mjs +41 -0
- package/lib/log.mjs +44 -0
- package/lib/panel.mjs +215 -0
- package/lib/store.mjs +264 -0
- package/lib/tools.mjs +398 -0
- package/lib/util.mjs +156 -0
- package/package.json +77 -4
package/lib/util.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// ---------------------------------------------------------------- 小工具
|
|
2
|
+
|
|
3
|
+
export function estTokens(text) {
|
|
4
|
+
let cjk = 0;
|
|
5
|
+
let other = 0;
|
|
6
|
+
for (const ch of String(text)) {
|
|
7
|
+
if (/\p{Script=Han}/u.test(ch)) cjk += 1;
|
|
8
|
+
else other += 1;
|
|
9
|
+
}
|
|
10
|
+
return Math.ceil(cjk + other / 4);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function clampText(text, maxTokens) {
|
|
14
|
+
const s = String(text ?? '');
|
|
15
|
+
if (estTokens(s) <= maxTokens) return s;
|
|
16
|
+
// 粗略按 token 比例截断,再收尾到最后一个换行
|
|
17
|
+
const ratio = maxTokens / Math.max(1, estTokens(s));
|
|
18
|
+
let cut = s.slice(0, Math.max(20, Math.floor(s.length * ratio)));
|
|
19
|
+
const nl = cut.lastIndexOf('\n');
|
|
20
|
+
if (nl > cut.length * 0.6) cut = cut.slice(0, nl);
|
|
21
|
+
return cut.trimEnd() + '\n…(画像已截断)';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function nowIso() {
|
|
25
|
+
return new Date().toISOString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function stamp(date = new Date()) {
|
|
29
|
+
const p = n => String(n).padStart(2, '0');
|
|
30
|
+
return `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function dayOf(iso) {
|
|
34
|
+
return typeof iso === 'string' && iso.length >= 10 ? iso.slice(0, 10) : '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 本地日期 YYYY-MM-DD(journal 按本地日切分)。 */
|
|
38
|
+
export function localDay(date = new Date()) {
|
|
39
|
+
const p = n => String(n).padStart(2, '0');
|
|
40
|
+
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 从 DSH 消息里抽出纯文本。 */
|
|
44
|
+
export function messageText(message) {
|
|
45
|
+
if (!message) return '';
|
|
46
|
+
if (typeof message === 'string') return message;
|
|
47
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
48
|
+
const texts = parts
|
|
49
|
+
.filter(p => p && (p.type === 'text' || typeof p.text === 'string'))
|
|
50
|
+
.map(p => String(p.text ?? ''))
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
if (texts.length) return texts.join('\n');
|
|
53
|
+
if (typeof message.content === 'string') return message.content;
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 做梦时用于判断"值得提炼"的线索词。 */
|
|
58
|
+
export const CANDIDATE_CUES = /(记住|记一下|记下|我喜欢|我讨厌|我不喜欢|我偏好|以后都|以后用|统一用|不要用|别再用|别再|下次|约定|决定|采用|规则|注意|重要|必须|千万别)/;
|
|
59
|
+
|
|
60
|
+
export function slugify(title) {
|
|
61
|
+
const s = String(title).trim().toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
|
|
63
|
+
.replace(/^-+|-+$/g, '');
|
|
64
|
+
return (s || 'memory').slice(0, 40);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function bigrams(text) {
|
|
68
|
+
const s = String(text).toLowerCase().replace(/\s+/g, ' ').trim();
|
|
69
|
+
const set = new Set();
|
|
70
|
+
for (let i = 0; i < s.length - 1; i++) set.add(s.slice(i, i + 2));
|
|
71
|
+
if (s.length === 1) set.add(s);
|
|
72
|
+
return set;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function jaccard(a, b) {
|
|
76
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
77
|
+
let inter = 0;
|
|
78
|
+
const [small, large] = a.size <= b.size ? [a, b] : [b, a];
|
|
79
|
+
for (const x of small) if (large.has(x)) inter++;
|
|
80
|
+
return inter / (a.size + b.size - inter);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function similar(a, b) {
|
|
84
|
+
return jaccard(bigrams(a), bigrams(b));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function out(obj) {
|
|
88
|
+
return JSON.parse(JSON.stringify(obj)); // 去掉 undefined,保证 lossless JSON
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------- 显著性 / 复习 / 睡眠
|
|
92
|
+
|
|
93
|
+
export const EXPLICIT_RE = /(记住|记一下|记下|务必|永远|千万别|绝不|必须)/;
|
|
94
|
+
export const CORRECTION_RE = /(不是|改用|纠正|其实|之前说错|更正|应该是)/;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 显著性打分 0-10(编码阶段:杏仁核/多巴胺式的"值不值得记")。
|
|
98
|
+
* 用户明说 > 纠正 > 绝对化措辞 > 新颖性 > 用户置顶。
|
|
99
|
+
*/
|
|
100
|
+
export function salienceFor({ title, content, importance, pin, novelty }) {
|
|
101
|
+
const text = `${title}\n${content}`;
|
|
102
|
+
let score = 3;
|
|
103
|
+
if (EXPLICIT_RE.test(text)) score += 4; // 明确要求记住
|
|
104
|
+
if (CORRECTION_RE.test(text)) score += 2; // 纠正(最高价值类型)
|
|
105
|
+
if (/(重要|关键|核心|底线|原则)/.test(text)) score += 1;
|
|
106
|
+
score += Math.round((novelty ?? 1) * 2); // 越新越值得记
|
|
107
|
+
if ((importance ?? 3) >= 4) score += 1;
|
|
108
|
+
if (pin) score += 1;
|
|
109
|
+
return Math.max(1, Math.min(10, score));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 初始稳定性(天):越重要 / 越显著,记得越久。 */
|
|
113
|
+
export function initialStability(importance, salience) {
|
|
114
|
+
const base = 3 + (importance ?? 3) * 2 + Math.round((salience ?? 5) / 2); // 约 8-13 天
|
|
115
|
+
return Math.min(90, Math.max(2, base));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function addDays(iso, days) {
|
|
119
|
+
const t = new Date(iso ?? nowIso()).getTime();
|
|
120
|
+
return new Date(t + days * 86400000).toISOString();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function isDue(entry, now = Date.now()) {
|
|
124
|
+
if (!entry?.nextReview) return false;
|
|
125
|
+
const t = new Date(entry.nextReview).getTime();
|
|
126
|
+
return Number.isFinite(t) && t <= now;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 复习一次:稳定性增长(间隔重复的"提取练习效应")。 */
|
|
130
|
+
export function reinforce(entry, factor = 1.3) {
|
|
131
|
+
const now = nowIso();
|
|
132
|
+
entry.hits = (entry.hits || 0) + 1;
|
|
133
|
+
entry.lastHit = now;
|
|
134
|
+
entry.lastReview = now;
|
|
135
|
+
if (entry.stability) entry.stability = Math.min(365, Math.max(1, entry.stability * factor));
|
|
136
|
+
else entry.stability = initialStability(entry.importance, entry.salience);
|
|
137
|
+
entry.reviews = (entry.reviews || 0) + 1;
|
|
138
|
+
entry.nextReview = addDays(now, Math.round(entry.stability));
|
|
139
|
+
return entry;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 记忆强度:显著性 × 衰减 × 复习命中(SHY 的"用进废退")。 */
|
|
143
|
+
export function strengthOf(entry, now = Date.now()) {
|
|
144
|
+
const ageDays = Math.max(0, (now - new Date(entry.updated || entry.created || nowIso()).getTime()) / 86400000);
|
|
145
|
+
const stability = entry.stability || 30;
|
|
146
|
+
const recency = Math.exp(-ageDays / Math.max(1, stability));
|
|
147
|
+
const salience = (entry.salience ?? 5) / 10;
|
|
148
|
+
return salience * recency * (1 + (entry.hits || 0) * 0.2);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const SECRET_RE = /(sk-[A-Za-z0-9]{16,})|(api[_-]?key\s*[:=]\s*\S{12,})|(password\s*[:=]\s*\S{6,})|(Bearer\s+[A-Za-z0-9._~+/=-]{20,})|(-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
152
|
+
|
|
153
|
+
/** 落盘前脱敏:journal 里可能混入用户粘贴的密钥。 */
|
|
154
|
+
export function redactSecrets(text) {
|
|
155
|
+
return String(text ?? '').replace(SECRET_RE, '[REDACTED]');
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-memory-fish",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Context-budgeted local memory for DeepSeek Harness: fixed 600-token injection, three-stage sleep consolidation, spaced repetition, temporal validity. Zero dependencies, zero server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"icon": "./icon.svg",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.mjs",
|
|
10
|
+
"./client": "./client/client.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.mjs",
|
|
15
|
+
"lib/*.mjs",
|
|
16
|
+
"client/client.js",
|
|
17
|
+
"cordis.patch.yml",
|
|
18
|
+
"icon.svg",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README.en.md",
|
|
21
|
+
"DESIGN.md",
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.19"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "node tests/smoke.mjs",
|
|
31
|
+
"check": "node --check index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"dsh",
|
|
35
|
+
"dsh-plugin",
|
|
36
|
+
"deepseek-harness",
|
|
37
|
+
"memory",
|
|
38
|
+
"local-first",
|
|
39
|
+
"token-budget",
|
|
40
|
+
"spaced-repetition",
|
|
41
|
+
"sleep-consolidation"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/GitHub-yxm-hub/dsh-memory-fish.git"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/GitHub-yxm-hub/dsh-memory-fish#readme",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/GitHub-yxm-hub/dsh-memory-fish/issues"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@deepseek-ai/cordis": ">=4.0.0",
|
|
57
|
+
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@deepseek-ai/cordis": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@deepseek-ai/dsh-tools": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"dsh": {
|
|
68
|
+
"bundle": {
|
|
69
|
+
"patch": "./cordis.patch.yml"
|
|
70
|
+
},
|
|
71
|
+
"client": {
|
|
72
|
+
"platform": "web",
|
|
73
|
+
"inject": [
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|