dsh-layered-memory 0.7.0 → 0.7.1
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.en.md +5 -0
- package/README.md +6 -1
- package/assets/img/Modes.png +0 -0
- package/dist/runtime-package-lock.json +982 -0
- package/dist/store/l0.js +7 -4
- package/dist/store/l1.js +7 -4
- package/dist/store/runtime-installer.d.ts +6 -1
- package/dist/store/runtime-installer.js +65 -26
- package/dist/store/sqlite.d.ts +15 -0
- package/dist/store/sqlite.js +83 -1
- package/package.json +1 -1
package/dist/store/l0.js
CHANGED
|
@@ -154,17 +154,20 @@ export class L0Store {
|
|
|
154
154
|
opts?.onProgress?.(done, total);
|
|
155
155
|
continue;
|
|
156
156
|
}
|
|
157
|
+
const pending = [];
|
|
157
158
|
chunk.forEach((c, j) => {
|
|
158
159
|
if (isZeroVector(vecs[j])) {
|
|
159
160
|
skipped++;
|
|
160
161
|
skippedNow.push(c.id);
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
-
written++;
|
|
165
|
-
else
|
|
166
|
-
failed++;
|
|
164
|
+
pending.push({ id: c.id, embedding: vecs[j] });
|
|
167
165
|
});
|
|
166
|
+
if (pending.length > 0) {
|
|
167
|
+
const ok = this.db.updateL0VecBatch(pending, '');
|
|
168
|
+
written += ok;
|
|
169
|
+
failed += pending.length - ok;
|
|
170
|
+
}
|
|
168
171
|
done += chunk.length;
|
|
169
172
|
opts?.onProgress?.(done, total);
|
|
170
173
|
}
|
package/dist/store/l1.js
CHANGED
|
@@ -221,17 +221,20 @@ export class L1Store {
|
|
|
221
221
|
opts?.onProgress?.(done, total);
|
|
222
222
|
continue;
|
|
223
223
|
}
|
|
224
|
+
const pending = [];
|
|
224
225
|
chunk.forEach((c, j) => {
|
|
225
226
|
if (isZeroVector(vecs[j])) {
|
|
226
227
|
skipped++;
|
|
227
228
|
skippedNow.push(c.id);
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
230
|
-
|
|
231
|
-
written++;
|
|
232
|
-
else
|
|
233
|
-
failed++;
|
|
231
|
+
pending.push({ id: c.id, embedding: vecs[j] });
|
|
234
232
|
});
|
|
233
|
+
if (pending.length > 0) {
|
|
234
|
+
const ok = this.db.updateL1VecBatch(pending);
|
|
235
|
+
written += ok;
|
|
236
|
+
failed += pending.length - ok;
|
|
237
|
+
}
|
|
235
238
|
done += chunk.length;
|
|
236
239
|
opts?.onProgress?.(done, total);
|
|
237
240
|
}
|
|
@@ -28,14 +28,17 @@ export declare class RuntimeInstaller {
|
|
|
28
28
|
private readonly target;
|
|
29
29
|
private readonly logger?;
|
|
30
30
|
private readonly spawnImpl;
|
|
31
|
+
/** 随包 lockfile 路径(测试可注入;默认取 dist 根下构建期拷入的资产)。 */
|
|
32
|
+
private readonly lockfileSource;
|
|
31
33
|
private progress;
|
|
32
34
|
private child;
|
|
33
35
|
private current;
|
|
34
|
-
/** 安装超时(npm 卡死不罕见:registry 停滞即永挂,applyBusy
|
|
36
|
+
/** 安装超时(npm 卡死不罕见:registry 停滞即永挂,applyBusy 会被锁死)。每次 spawn 独立计时。 */
|
|
35
37
|
private static INSTALL_TIMEOUT_MS;
|
|
36
38
|
constructor(dataDir: string, targetVersion: string, opts?: {
|
|
37
39
|
logger?: MemoryLogger;
|
|
38
40
|
spawnImpl?: SpawnImpl;
|
|
41
|
+
lockfileSource?: string;
|
|
39
42
|
});
|
|
40
43
|
/** 包内模块名(与钉死版本一起构成安装目标)。 */
|
|
41
44
|
static packageName: string;
|
|
@@ -56,5 +59,7 @@ export declare class RuntimeInstaller {
|
|
|
56
59
|
/** 从 runtime 目录解析已安装的 transformers 模块(LocalEmbeddingService 用)。 */
|
|
57
60
|
resolveModule(): unknown;
|
|
58
61
|
private pushLine;
|
|
62
|
+
/** 跑一次 npm 子进程(采集尾行 + 超时 kill),返回退出码(null = 被杀死/启动失败)。 */
|
|
63
|
+
private runNpm;
|
|
59
64
|
private installOnce;
|
|
60
65
|
}
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* 插件 npm 包本体不带重依赖,不用本地嵌入的用户零成本。
|
|
5
5
|
*
|
|
6
6
|
* - 安装位置:数据目录 runtime/(自带 package.json 锚定,防 npm 向上层目录逃逸安装);
|
|
7
|
-
* - 子进程 npm
|
|
7
|
+
* - 子进程 npm ci --ignore-scripts --no-audit --no-fund,钉死精确版本 + 随包 lockfile
|
|
8
|
+
* (resources/runtime-package-lock.json,构建期拷入 dist/)锁定完整传递依赖树——
|
|
9
|
+
* 纯 install 只锁直接依赖的精确版本,传递依赖按 semver 浮动解析,registry 端
|
|
10
|
+
* 后续发布/投毒会随安装时间漂移;lockfile 把树冻结在作者侧。ci 失败(lock 与
|
|
11
|
+
* package.json 漂移等)自动回退 npm install 精确版本(可用性优先,树不再受锁);
|
|
8
12
|
* - 进度(用户硬性要求:不能傻等):npm 非交互模式无百分比 API,采用不确定进度——
|
|
9
13
|
* 已耗时 + 子进程 stdout/stderr 尾行实时流出 + 可 kill;
|
|
10
14
|
* - 幂等:已装版本 == 目标版本直接就绪;版本漂移(插件升级换了钉死版本)重装覆盖。
|
|
@@ -13,6 +17,7 @@ import { spawn } from 'node:child_process';
|
|
|
13
17
|
import { createRequire } from 'node:module';
|
|
14
18
|
import { promises as fs } from 'node:fs';
|
|
15
19
|
import * as path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
16
21
|
/** 钉死的 transformers.js 版本(D8:精确版本,升级插件时在此变更并重装运行时)。 */
|
|
17
22
|
export const PINNED_TRANSFORMERS_VERSION = '4.2.0';
|
|
18
23
|
export class RuntimeInstaller {
|
|
@@ -20,15 +25,19 @@ export class RuntimeInstaller {
|
|
|
20
25
|
target;
|
|
21
26
|
logger;
|
|
22
27
|
spawnImpl;
|
|
28
|
+
/** 随包 lockfile 路径(测试可注入;默认取 dist 根下构建期拷入的资产)。 */
|
|
29
|
+
lockfileSource;
|
|
23
30
|
progress;
|
|
24
31
|
child = null;
|
|
25
32
|
current = null;
|
|
26
|
-
/** 安装超时(npm 卡死不罕见:registry 停滞即永挂,applyBusy
|
|
33
|
+
/** 安装超时(npm 卡死不罕见:registry 停滞即永挂,applyBusy 会被锁死)。每次 spawn 独立计时。 */
|
|
27
34
|
static INSTALL_TIMEOUT_MS = 10 * 60_000;
|
|
28
35
|
constructor(dataDir, targetVersion, opts) {
|
|
29
36
|
this.runtimeDir = path.join(dataDir, 'runtime');
|
|
30
37
|
this.target = targetVersion;
|
|
31
38
|
this.logger = opts?.logger;
|
|
39
|
+
this.lockfileSource =
|
|
40
|
+
opts?.lockfileSource ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'runtime-package-lock.json');
|
|
32
41
|
this.spawnImpl =
|
|
33
42
|
opts?.spawnImpl ??
|
|
34
43
|
((command, args, cwd) => {
|
|
@@ -127,41 +136,71 @@ export class RuntimeInstaller {
|
|
|
127
136
|
if (lines.length > 5)
|
|
128
137
|
lines.splice(0, lines.length - 5);
|
|
129
138
|
}
|
|
139
|
+
/** 跑一次 npm 子进程(采集尾行 + 超时 kill),返回退出码(null = 被杀死/启动失败)。 */
|
|
140
|
+
async runNpm(args) {
|
|
141
|
+
const child = this.spawnImpl('npm', args, this.runtimeDir);
|
|
142
|
+
this.child = child;
|
|
143
|
+
child.onStdout((l) => this.pushLine(l));
|
|
144
|
+
child.onStderr((l) => this.pushLine(l));
|
|
145
|
+
const timeout = setTimeout(() => {
|
|
146
|
+
this.pushLine('安装超时(10 分钟),终止子进程');
|
|
147
|
+
child.kill();
|
|
148
|
+
}, RuntimeInstaller.INSTALL_TIMEOUT_MS);
|
|
149
|
+
const code = await child.exited;
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
this.child = null;
|
|
152
|
+
return code;
|
|
153
|
+
}
|
|
130
154
|
async installOnce() {
|
|
131
|
-
// 锚定 package.json
|
|
155
|
+
// 锚定 package.json(带精确依赖):没有它 npm 会向上层目录找最近的 package.json 安装(逃逸事故);
|
|
156
|
+
// npm ci 还要求它与 lockfile 根条目一致——每次安装都写规范化形状,覆盖历史遗留/被 npm 改写的副本。
|
|
132
157
|
await fs.mkdir(this.runtimeDir, { recursive: true });
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
158
|
+
const manifest = {
|
|
159
|
+
name: 'dsh-memory-runtime',
|
|
160
|
+
private: true,
|
|
161
|
+
dependencies: { [RuntimeInstaller.packageName]: this.target },
|
|
162
|
+
};
|
|
163
|
+
await fs.writeFile(path.join(this.runtimeDir, 'package.json'), JSON.stringify(manifest, null, 2));
|
|
140
164
|
this.progress = {
|
|
141
165
|
phase: 'installing',
|
|
142
166
|
targetVersion: this.target,
|
|
143
167
|
installedVersion: await this.installedVersion(),
|
|
144
168
|
startedAt: Date.now(),
|
|
145
169
|
elapsedMs: 0,
|
|
146
|
-
lastLines: [
|
|
170
|
+
lastLines: [],
|
|
147
171
|
};
|
|
148
|
-
const child = this.spawnImpl('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', '--loglevel', 'notice', `${RuntimeInstaller.packageName}@${this.target}`], this.runtimeDir);
|
|
149
|
-
this.child = child;
|
|
150
|
-
child.onStdout((l) => this.pushLine(l));
|
|
151
|
-
child.onStderr((l) => this.pushLine(l));
|
|
152
|
-
const timeout = setTimeout(() => {
|
|
153
|
-
this.pushLine('安装超时(10 分钟),终止子进程');
|
|
154
|
-
child.kill();
|
|
155
|
-
}, RuntimeInstaller.INSTALL_TIMEOUT_MS);
|
|
156
172
|
this.logger?.info(`[memory] 运行时安装开始: ${RuntimeInstaller.packageName}@${this.target} → ${this.runtimeDir}`);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
// 首选 npm ci + 随包 lockfile(传递依赖树冻结在作者侧);lockfile 资产缺失(npm 包被裁剪等)
|
|
174
|
+
// 或 ci 失败(lock 与锚定版本漂移等)回退 npm install 精确版本——可用性优先。
|
|
175
|
+
let code = null;
|
|
176
|
+
let usedCi = false;
|
|
177
|
+
try {
|
|
178
|
+
const lock = await fs.readFile(this.lockfileSource, 'utf8');
|
|
179
|
+
await fs.writeFile(path.join(this.runtimeDir, 'package-lock.json'), lock);
|
|
180
|
+
this.pushLine(`npm ci(随包 lockfile 锁定依赖树,@${this.target},--ignore-scripts)`);
|
|
181
|
+
usedCi = true;
|
|
182
|
+
code = await this.runNpm(['ci', '--ignore-scripts', '--no-audit', '--no-fund', '--loglevel', 'notice']);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* 无随包 lockfile,直接走 install 回退 */
|
|
186
|
+
}
|
|
187
|
+
if (!usedCi || code !== 0) {
|
|
188
|
+
if (usedCi) {
|
|
189
|
+
this.pushLine('npm ci 失败(lockfile 与钉死版本漂移?),回退 npm install');
|
|
190
|
+
this.logger?.warn('[memory] 运行时 npm ci 失败,回退 npm install(传递依赖不再受随包 lockfile 锁定)');
|
|
191
|
+
}
|
|
192
|
+
this.pushLine(`npm install ${RuntimeInstaller.packageName}@${this.target}(--ignore-scripts)`);
|
|
193
|
+
code = await this.runNpm([
|
|
194
|
+
'install',
|
|
195
|
+
'--ignore-scripts',
|
|
196
|
+
'--no-audit',
|
|
197
|
+
'--no-fund',
|
|
198
|
+
'--loglevel',
|
|
199
|
+
'notice',
|
|
200
|
+
`${RuntimeInstaller.packageName}@${this.target}`,
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
163
203
|
this.progress.elapsedMs = Date.now() - this.progress.startedAt;
|
|
164
|
-
this.child = null;
|
|
165
204
|
const version = await this.installedVersion();
|
|
166
205
|
this.progress.installedVersion = version;
|
|
167
206
|
if (this.progress.phase === 'cancelled') {
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -176,6 +176,21 @@ export declare class MemoryDb {
|
|
|
176
176
|
/** 只更新向量行(重嵌入用)。 */
|
|
177
177
|
updateL1Vec(id: string, embedding: Float32Array): boolean;
|
|
178
178
|
updateL0Vec(id: string, embedding: Float32Array, recordedAt: string): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* 批量更新 L1 向量行(重嵌入热路径):单事务写入整批,替代逐条裸写——
|
|
181
|
+
* 逐条每行一次隐式事务,批量场景(万级记录重嵌)开销集中在 fsync 上。
|
|
182
|
+
* 整批失败回退逐条:好行照常入库,坏行只丢自身(向量行 id 寻址,无顺序依赖)。
|
|
183
|
+
* 返回成功写入的行数(零向量行防御性跳过、不计入)。
|
|
184
|
+
*/
|
|
185
|
+
updateL1VecBatch(items: Array<{
|
|
186
|
+
id: string;
|
|
187
|
+
embedding: Float32Array;
|
|
188
|
+
}>): number;
|
|
189
|
+
/** L0 版 updateL1VecBatch(语义同:单事务 + 失败回退逐条)。recordedAt 整批统一。 */
|
|
190
|
+
updateL0VecBatch(items: Array<{
|
|
191
|
+
id: string;
|
|
192
|
+
embedding: Float32Array;
|
|
193
|
+
}>, recordedAt: string): number;
|
|
179
194
|
close(): void;
|
|
180
195
|
}
|
|
181
196
|
/** 全零向量(cosine 未定义,不可入向量表)。reindex 侧用它区分"不可嵌入"与"写入失败"。 */
|
package/dist/store/sqlite.js
CHANGED
|
@@ -72,9 +72,12 @@ export class MemoryDb {
|
|
|
72
72
|
mkdirSync(dbDir, { recursive: true });
|
|
73
73
|
const { DatabaseSync: DbSync } = require('node:sqlite');
|
|
74
74
|
this.db = new DbSync(dbPath, { allowExtension: true });
|
|
75
|
-
// 并发读优化 + 有界内存(照搬官方 PRAGMA
|
|
75
|
+
// 并发读优化 + 有界内存(照搬官方 PRAGMA 组合,synchronous 为本仓新增:
|
|
76
|
+
// WAL 下官方推荐 NORMAL——批量写从"每事务一次 fsync"降为"每 checkpoint 一次",
|
|
77
|
+
// 重嵌入/导入提速明显;代价仅是断电时丢最后若干已提交事务(只丢不损,无损坏风险))
|
|
76
78
|
this.db.exec('PRAGMA busy_timeout = 5000');
|
|
77
79
|
this.db.exec('PRAGMA journal_mode = WAL');
|
|
80
|
+
this.db.exec('PRAGMA synchronous = NORMAL');
|
|
78
81
|
this.db.exec('PRAGMA cache_size = -65536');
|
|
79
82
|
this.db.exec('PRAGMA mmap_size = 134217728');
|
|
80
83
|
this.db.exec('PRAGMA wal_autocheckpoint = 1000');
|
|
@@ -1179,6 +1182,85 @@ export class MemoryDb {
|
|
|
1179
1182
|
return false;
|
|
1180
1183
|
}
|
|
1181
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* 批量更新 L1 向量行(重嵌入热路径):单事务写入整批,替代逐条裸写——
|
|
1187
|
+
* 逐条每行一次隐式事务,批量场景(万级记录重嵌)开销集中在 fsync 上。
|
|
1188
|
+
* 整批失败回退逐条:好行照常入库,坏行只丢自身(向量行 id 寻址,无顺序依赖)。
|
|
1189
|
+
* 返回成功写入的行数(零向量行防御性跳过、不计入)。
|
|
1190
|
+
*/
|
|
1191
|
+
updateL1VecBatch(items) {
|
|
1192
|
+
if (this.degraded || !this.stmtDeleteL1Vec || !this.stmtInsertL1Vec || items.length === 0)
|
|
1193
|
+
return 0;
|
|
1194
|
+
try {
|
|
1195
|
+
this.db.exec('BEGIN');
|
|
1196
|
+
try {
|
|
1197
|
+
let written = 0;
|
|
1198
|
+
for (const it of items) {
|
|
1199
|
+
if (isZeroVector(it.embedding))
|
|
1200
|
+
continue;
|
|
1201
|
+
this.stmtDeleteL1Vec.run(it.id);
|
|
1202
|
+
this.stmtInsertL1Vec.run(it.id, vecToBuffer(it.embedding), new Date().toISOString());
|
|
1203
|
+
written++;
|
|
1204
|
+
}
|
|
1205
|
+
this.db.exec('COMMIT');
|
|
1206
|
+
return written;
|
|
1207
|
+
}
|
|
1208
|
+
catch (err) {
|
|
1209
|
+
try {
|
|
1210
|
+
this.db.exec('ROLLBACK');
|
|
1211
|
+
}
|
|
1212
|
+
catch {
|
|
1213
|
+
/* ignore */
|
|
1214
|
+
}
|
|
1215
|
+
throw err;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
this.logger?.warn(`${TAG} L1 向量批量写入失败,回退逐条: ${err instanceof Error ? err.message : String(err)}`);
|
|
1220
|
+
let ok = 0;
|
|
1221
|
+
for (const it of items)
|
|
1222
|
+
if (this.updateL1Vec(it.id, it.embedding))
|
|
1223
|
+
ok++;
|
|
1224
|
+
return ok;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
/** L0 版 updateL1VecBatch(语义同:单事务 + 失败回退逐条)。recordedAt 整批统一。 */
|
|
1228
|
+
updateL0VecBatch(items, recordedAt) {
|
|
1229
|
+
if (this.degraded || !this.stmtDeleteL0Vec || !this.stmtInsertL0Vec || items.length === 0)
|
|
1230
|
+
return 0;
|
|
1231
|
+
try {
|
|
1232
|
+
this.db.exec('BEGIN');
|
|
1233
|
+
try {
|
|
1234
|
+
let written = 0;
|
|
1235
|
+
for (const it of items) {
|
|
1236
|
+
if (isZeroVector(it.embedding))
|
|
1237
|
+
continue;
|
|
1238
|
+
this.stmtDeleteL0Vec.run(it.id);
|
|
1239
|
+
this.stmtInsertL0Vec.run(it.id, vecToBuffer(it.embedding), recordedAt);
|
|
1240
|
+
written++;
|
|
1241
|
+
}
|
|
1242
|
+
this.db.exec('COMMIT');
|
|
1243
|
+
return written;
|
|
1244
|
+
}
|
|
1245
|
+
catch (err) {
|
|
1246
|
+
try {
|
|
1247
|
+
this.db.exec('ROLLBACK');
|
|
1248
|
+
}
|
|
1249
|
+
catch {
|
|
1250
|
+
/* ignore */
|
|
1251
|
+
}
|
|
1252
|
+
throw err;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
catch (err) {
|
|
1256
|
+
this.logger?.warn(`${TAG} L0 向量批量写入失败,回退逐条: ${err instanceof Error ? err.message : String(err)}`);
|
|
1257
|
+
let ok = 0;
|
|
1258
|
+
for (const it of items)
|
|
1259
|
+
if (this.updateL0Vec(it.id, it.embedding, recordedAt))
|
|
1260
|
+
ok++;
|
|
1261
|
+
return ok;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1182
1264
|
close() {
|
|
1183
1265
|
try {
|
|
1184
1266
|
this.db.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-layered-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|