dsh-layered-memory 0.6.0 → 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/README.en.md +101 -81
- package/README.md +91 -71
- package/assets/img/Hero.png +0 -0
- package/assets/img/Layers.png +0 -0
- package/assets/img/Modes.png +0 -0
- package/assets/img/ui-dark.jpg +0 -0
- package/assets/img/ui-light.jpg +0 -0
- package/assets/readme/flow.svg +189 -0
- package/assets/readme/storage.svg +115 -0
- package/dist/client.js +1242 -325
- package/dist/config.d.ts +12 -0
- package/dist/config.js +20 -17
- package/dist/hooks/capture.d.ts +17 -1
- package/dist/hooks/capture.js +45 -11
- package/dist/hooks/recall.d.ts +7 -0
- package/dist/hooks/recall.js +20 -4
- package/dist/index.d.ts +8 -0
- package/dist/index.js +112 -44
- package/dist/pipeline/rebuild.d.ts +80 -0
- package/dist/pipeline/rebuild.js +307 -0
- package/dist/pipeline/runner.d.ts +42 -4
- package/dist/pipeline/runner.js +125 -21
- package/dist/settings.js +79 -20
- package/dist/stats.d.ts +8 -1
- package/dist/stats.js +156 -14
- package/dist/store/download-queue.d.ts +71 -0
- package/dist/store/download-queue.js +313 -0
- package/dist/store/embedding-source.d.ts +160 -0
- package/dist/store/embedding-source.js +421 -0
- package/dist/store/embedding.d.ts +3 -1
- package/dist/store/embedding.js +5 -0
- package/dist/store/l0.d.ts +13 -5
- package/dist/store/l0.js +33 -6
- package/dist/store/l1.d.ts +15 -5
- package/dist/store/l1.js +40 -15
- package/dist/store/local-embedding.d.ts +64 -0
- package/dist/store/local-embedding.js +120 -0
- package/dist/store/model-catalog.d.ts +45 -0
- package/dist/store/model-catalog.js +78 -0
- package/dist/store/pending.d.ts +15 -0
- package/dist/store/pending.js +56 -0
- package/dist/store/runtime-installer.d.ts +60 -0
- package/dist/store/runtime-installer.js +181 -0
- package/dist/store/sqlite.d.ts +68 -7
- package/dist/store/sqlite.js +414 -71
- package/dist/store/state.d.ts +6 -0
- package/dist/store/state.js +11 -0
- package/dist/tools/index.js +6 -4
- package/dist/util/filelog.d.ts +2 -0
- package/dist/util/filelog.js +20 -3
- package/package.json +1 -1
- package/assets/readme/hero.svg +0 -58
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时安装器(D8 决策):本地嵌入的推理运行时(@huggingface/transformers,
|
|
3
|
+
* 纯库、二进制随 npm 包分发、零 postinstall)在用户首次开启本地嵌入时才安装——
|
|
4
|
+
* 插件 npm 包本体不带重依赖,不用本地嵌入的用户零成本。
|
|
5
|
+
*
|
|
6
|
+
* - 安装位置:数据目录 runtime/(自带 package.json 锚定,防 npm 向上层目录逃逸安装);
|
|
7
|
+
* - 子进程 npm install --ignore-scripts --no-audit --no-fund,钉死精确版本;
|
|
8
|
+
* - 进度(用户硬性要求:不能傻等):npm 非交互模式无百分比 API,采用不确定进度——
|
|
9
|
+
* 已耗时 + 子进程 stdout/stderr 尾行实时流出 + 可 kill;
|
|
10
|
+
* - 幂等:已装版本 == 目标版本直接就绪;版本漂移(插件升级换了钉死版本)重装覆盖。
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { promises as fs } from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
/** 钉死的 transformers.js 版本(D8:精确版本,升级插件时在此变更并重装运行时)。 */
|
|
17
|
+
export const PINNED_TRANSFORMERS_VERSION = '4.2.0';
|
|
18
|
+
export class RuntimeInstaller {
|
|
19
|
+
runtimeDir;
|
|
20
|
+
target;
|
|
21
|
+
logger;
|
|
22
|
+
spawnImpl;
|
|
23
|
+
progress;
|
|
24
|
+
child = null;
|
|
25
|
+
current = null;
|
|
26
|
+
/** 安装超时(npm 卡死不罕见:registry 停滞即永挂,applyBusy 会被锁死)。 */
|
|
27
|
+
static INSTALL_TIMEOUT_MS = 10 * 60_000;
|
|
28
|
+
constructor(dataDir, targetVersion, opts) {
|
|
29
|
+
this.runtimeDir = path.join(dataDir, 'runtime');
|
|
30
|
+
this.target = targetVersion;
|
|
31
|
+
this.logger = opts?.logger;
|
|
32
|
+
this.spawnImpl =
|
|
33
|
+
opts?.spawnImpl ??
|
|
34
|
+
((command, args, cwd) => {
|
|
35
|
+
const child = spawn(command, args, {
|
|
36
|
+
cwd,
|
|
37
|
+
// Windows 上 .cmd 必须走 shell(Node 20+ 安全限制);参数全部来自插件常量,无注入面
|
|
38
|
+
shell: process.platform === 'win32',
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
const spawned = {
|
|
42
|
+
onStdout(cb) {
|
|
43
|
+
child.stdout?.on('data', (d) => String(d).split(/\r?\n/).forEach((l) => l && cb(l)));
|
|
44
|
+
},
|
|
45
|
+
onStderr(cb) {
|
|
46
|
+
child.stderr?.on('data', (d) => String(d).split(/\r?\n/).forEach((l) => l && cb(l)));
|
|
47
|
+
},
|
|
48
|
+
kill: () => child.kill(),
|
|
49
|
+
// 'error'(如 ENOENT:PATH 无 npm)只发 error 不发 close——不监听会永挂
|
|
50
|
+
exited: new Promise((resolve) => {
|
|
51
|
+
child.on('close', (code) => resolve(code));
|
|
52
|
+
child.on('error', () => resolve(null));
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
return spawned;
|
|
56
|
+
});
|
|
57
|
+
this.progress = {
|
|
58
|
+
phase: 'idle',
|
|
59
|
+
targetVersion: targetVersion,
|
|
60
|
+
installedVersion: null,
|
|
61
|
+
startedAt: 0,
|
|
62
|
+
elapsedMs: 0,
|
|
63
|
+
lastLines: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** 包内模块名(与钉死版本一起构成安装目标)。 */
|
|
67
|
+
static packageName = '@huggingface/transformers';
|
|
68
|
+
/** 进度快照。 */
|
|
69
|
+
getProgress() {
|
|
70
|
+
const elapsed = this.progress.phase === 'installing' ? Date.now() - this.progress.startedAt : this.progress.elapsedMs;
|
|
71
|
+
return { ...this.progress, lastLines: [...this.progress.lastLines], elapsedMs: elapsed };
|
|
72
|
+
}
|
|
73
|
+
pkgJsonPath() {
|
|
74
|
+
return path.join(this.runtimeDir, 'node_modules', RuntimeInstaller.packageName, 'package.json');
|
|
75
|
+
}
|
|
76
|
+
/** 已就位版本(读 package.json;未安装返回 null)。 */
|
|
77
|
+
async installedVersion() {
|
|
78
|
+
try {
|
|
79
|
+
const raw = await fs.readFile(this.pkgJsonPath(), 'utf8');
|
|
80
|
+
const pkg = JSON.parse(raw);
|
|
81
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** 是否就绪(版本精确匹配目标)。 */
|
|
88
|
+
async isReady() {
|
|
89
|
+
return (await this.installedVersion()) === this.target;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 确保运行时就位:版本匹配直接就绪;否则安装(忙时并 await 同一次任务)。
|
|
93
|
+
* 返回是否就绪(失败/取消返回 false 并在 progress.error 说明原因)。
|
|
94
|
+
*/
|
|
95
|
+
async ensure() {
|
|
96
|
+
if (await this.isReady()) {
|
|
97
|
+
this.progress.phase = 'ready';
|
|
98
|
+
this.progress.installedVersion = this.target;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (this.current)
|
|
102
|
+
return this.current;
|
|
103
|
+
this.current = this.installOnce();
|
|
104
|
+
try {
|
|
105
|
+
return await this.current;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.current = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** 取消安装(kill 子进程;node_modules 残留无害,npm 幂等重装)。 */
|
|
112
|
+
cancel() {
|
|
113
|
+
if (this.progress.phase !== 'installing' || !this.child)
|
|
114
|
+
return false;
|
|
115
|
+
this.progress.phase = 'cancelled';
|
|
116
|
+
this.child.kill();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/** 从 runtime 目录解析已安装的 transformers 模块(LocalEmbeddingService 用)。 */
|
|
120
|
+
resolveModule() {
|
|
121
|
+
const req = createRequire(path.join(this.runtimeDir, 'package.json'));
|
|
122
|
+
return req(RuntimeInstaller.packageName);
|
|
123
|
+
}
|
|
124
|
+
pushLine(line) {
|
|
125
|
+
const lines = this.progress.lastLines;
|
|
126
|
+
lines.push(line.length > 300 ? line.slice(0, 300) + '…' : line);
|
|
127
|
+
if (lines.length > 5)
|
|
128
|
+
lines.splice(0, lines.length - 5);
|
|
129
|
+
}
|
|
130
|
+
async installOnce() {
|
|
131
|
+
// 锚定 package.json:没有它 npm 会向上层目录找最近的 package.json 安装(逃逸事故)
|
|
132
|
+
await fs.mkdir(this.runtimeDir, { recursive: true });
|
|
133
|
+
const manifestPath = path.join(this.runtimeDir, 'package.json');
|
|
134
|
+
try {
|
|
135
|
+
await fs.access(manifestPath);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
await fs.writeFile(manifestPath, JSON.stringify({ name: 'dsh-memory-runtime', private: true }, null, 2));
|
|
139
|
+
}
|
|
140
|
+
this.progress = {
|
|
141
|
+
phase: 'installing',
|
|
142
|
+
targetVersion: this.target,
|
|
143
|
+
installedVersion: await this.installedVersion(),
|
|
144
|
+
startedAt: Date.now(),
|
|
145
|
+
elapsedMs: 0,
|
|
146
|
+
lastLines: [`npm install ${RuntimeInstaller.packageName}@${this.target}(--ignore-scripts)`],
|
|
147
|
+
};
|
|
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
|
+
this.logger?.info(`[memory] 运行时安装开始: ${RuntimeInstaller.packageName}@${this.target} → ${this.runtimeDir}`);
|
|
157
|
+
const code = await new Promise((resolve) => {
|
|
158
|
+
void child.exited.then((c) => {
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
resolve(c);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
this.progress.elapsedMs = Date.now() - this.progress.startedAt;
|
|
164
|
+
this.child = null;
|
|
165
|
+
const version = await this.installedVersion();
|
|
166
|
+
this.progress.installedVersion = version;
|
|
167
|
+
if (this.progress.phase === 'cancelled') {
|
|
168
|
+
this.logger?.warn('[memory] 运行时安装已取消(残留无害,重装幂等)');
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (code === 0 && version === this.target) {
|
|
172
|
+
this.progress.phase = 'ready';
|
|
173
|
+
this.logger?.info(`[memory] 运行时安装完成: v${version}`);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
this.progress.phase = 'error';
|
|
177
|
+
this.progress.error = `npm 退出码 ${code ?? '被杀死'}${version ? `(就位版本 ${version})` : '(模块未就位)'}`;
|
|
178
|
+
this.logger?.warn(`[memory] 运行时安装失败: ${this.progress.error}`);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -28,7 +28,9 @@ export declare class MemoryDb {
|
|
|
28
28
|
private degraded;
|
|
29
29
|
private ftsAvailable;
|
|
30
30
|
private vecLoaded;
|
|
31
|
-
private
|
|
31
|
+
private vecLoadWarned;
|
|
32
|
+
/** 向量维度:活切换嵌入源(D5)时会变——vec0 表随维度重建。 */
|
|
33
|
+
private dimensions;
|
|
32
34
|
private readonly logger?;
|
|
33
35
|
private stmtUpsertL1;
|
|
34
36
|
private stmtGetL1;
|
|
@@ -41,6 +43,7 @@ export declare class MemoryDb {
|
|
|
41
43
|
private stmtL1FtsSearch;
|
|
42
44
|
private stmtL1FtsSearchFamily;
|
|
43
45
|
private stmtUpsertL0;
|
|
46
|
+
private stmtGetL0;
|
|
44
47
|
private stmtDeleteL0Vec?;
|
|
45
48
|
private stmtInsertL0Vec?;
|
|
46
49
|
private stmtSearchL0Vec?;
|
|
@@ -55,6 +58,21 @@ export declare class MemoryDb {
|
|
|
55
58
|
* providerInfo 变化(provider/model/维度)时 drop 向量表并返回 needsReindex。
|
|
56
59
|
*/
|
|
57
60
|
init(providerInfo?: EmbeddingProviderInfo): StoreInitResult;
|
|
61
|
+
/** 惰性加载 sqlite-vec(纯 FTS 起步后切本地嵌入时补加载);失败只停用向量能力并告警一次。 */
|
|
62
|
+
private ensureVecLoaded;
|
|
63
|
+
/**
|
|
64
|
+
* 活切换嵌入源(D5):provider/model/维度任一变化 → drop 向量表按新维度重建,
|
|
65
|
+
* 返回 needsReindex=true(调用方后台重嵌,全部成功后 markEmbeddingSynced);
|
|
66
|
+
* 配置未变化 → false(切回同一模型不重嵌)。
|
|
67
|
+
* 新维度 > 0 但 sqlite-vec 不可用 → ok=false(调用方向用户说明,维持 FTS)。
|
|
68
|
+
*/
|
|
69
|
+
swapProvider(info: EmbeddingProviderInfo): {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
needsReindex: boolean;
|
|
72
|
+
error?: string;
|
|
73
|
+
};
|
|
74
|
+
/** l1_vec 物理表的向量维度(建表 DDL 里的 float[N]);无表返回 null。 */
|
|
75
|
+
private physicalVecDims;
|
|
58
76
|
private initSchema;
|
|
59
77
|
private prepareL1VecStatements;
|
|
60
78
|
private prepareL0VecStatements;
|
|
@@ -66,15 +84,35 @@ export declare class MemoryDb {
|
|
|
66
84
|
private readEmbeddingMeta;
|
|
67
85
|
private writeEmbeddingMeta;
|
|
68
86
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
87
|
+
* 持久化 embedding meta(语义:物理向量表当前对应的 provider/维度)。
|
|
88
|
+
* 活切换在 swapProvider 成功后即写(表已是新维度);启动/补齐链在
|
|
89
|
+
* 缺失向量补齐收敛(missing=0)后写——缺失行补齐判据是行数差,
|
|
90
|
+
* 不依赖 meta(review P7 语义在 backfill 计数判据下仍然收敛)。
|
|
72
91
|
*/
|
|
73
92
|
markEmbeddingSynced(info: EmbeddingProviderInfo): void;
|
|
74
93
|
/** upsert 一条 L1(元数据 + FTS 同步;embedding 非零时写向量)。失败返回 false 不抛。 */
|
|
75
94
|
upsertL1(record: MemoryRecord, embedding?: Float32Array): boolean;
|
|
76
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* 批量 upsert L1(单事务;与单条同语义:FTS 失败整批回滚)。
|
|
97
|
+
* 追加/导入热路径用它——逐条开事务在 WAL FULL 下每条一次 fsync。
|
|
98
|
+
* 整批失败时回退逐条写入:好记录照常入库、坏记录只丢自身——否则
|
|
99
|
+
* JSONL 事实源已先行追加,检索库却整批缺失且无自动重导路径(批次空洞)。
|
|
100
|
+
*/
|
|
101
|
+
upsertL1Batch(records: MemoryRecord[], embeddings?: Array<Float32Array | undefined>): boolean;
|
|
102
|
+
/** 事务内的单条写入体(upsertL1 / upsertL1Batch 共用;调用方负责 BEGIN/COMMIT)。 */
|
|
103
|
+
private upsertL1InTx;
|
|
104
|
+
/** 批量删除 L1(元数据 + 向量 + FTS),返回删除条数。IN 按 ≤900 分块(避变量数上限)。 */
|
|
77
105
|
deleteL1Batch(ids: string[]): number;
|
|
106
|
+
/** 按块缓存的 IN 语句(表名/动作/尺寸 → 预编译语句):热路径不再每次动态 prepare。 */
|
|
107
|
+
private readonly inStmts;
|
|
108
|
+
private inStatement;
|
|
109
|
+
/**
|
|
110
|
+
* 清空 L1 检索库全部数据(重建用)。records/FTS 直接 DELETE;
|
|
111
|
+
* 向量表走 DROP + 重建(vec0 的全表 DELETE 语义不可靠,dropVectorTables
|
|
112
|
+
* 会连 l0_vec 一起删——L0 向量必须保留——故此处单独处理 l1_vec)。
|
|
113
|
+
* L0 表与 embedding_meta 不动:backfill 的行数比对天然重新一致。
|
|
114
|
+
*/
|
|
115
|
+
clearL1(): boolean;
|
|
78
116
|
countL1(): number;
|
|
79
117
|
/** 全量读取(调试/迁移/重嵌入用;检索请走 FTS/向量)。 */
|
|
80
118
|
getAllL1(): MemoryRecord[];
|
|
@@ -101,21 +139,44 @@ export declare class MemoryDb {
|
|
|
101
139
|
countL0(): number;
|
|
102
140
|
/** 统计 recorded_at >= iso 的消息数(状态面板"今日捕获"用)。 */
|
|
103
141
|
countL0Since(iso: string): number;
|
|
142
|
+
/** L0 全量列举(重建快照用;按时间升序,事务一致性避开 JSONL 追加竞态)。 */
|
|
143
|
+
listL0All(): L0MessageRecord[];
|
|
144
|
+
/** 重建成本预估(一次全表聚合:会话数 / 消息数 / 字符量)。 */
|
|
145
|
+
l0RebuildEstimate(): {
|
|
146
|
+
sessions: number;
|
|
147
|
+
messages: number;
|
|
148
|
+
chars: number;
|
|
149
|
+
};
|
|
104
150
|
/** 向量表行数(backfill 判据:与元数据行数的差值即缺失向量数;不可用时返回 -1)。 */
|
|
105
151
|
countL1Vec(): number;
|
|
106
152
|
countL0Vec(): number;
|
|
107
153
|
searchL0Fts(query: string, limit: number): L0SearchHit[];
|
|
108
154
|
searchL0Vector(embedding: Float32Array, topK: number): L0SearchHit[];
|
|
109
|
-
|
|
155
|
+
/** L1 缺失向量的记录数(排除 skip 集后的补齐判据;向量能力不可用返回 -1)。 */
|
|
156
|
+
countL1VecMissing(exclude?: Set<string>): number;
|
|
157
|
+
/** L0 缺失向量的记录数(同上)。 */
|
|
158
|
+
countL0VecMissing(exclude?: Set<string>): number;
|
|
159
|
+
/**
|
|
160
|
+
* 待重嵌入的 L1:只取缺失向量的记录(增量),排除 skip 集里已判定
|
|
161
|
+
* "当前 provider 下不可嵌入(零向量)"的 id——缺 1 条不再全量重嵌,
|
|
162
|
+
* 零向量记录也不再反复喂给 embeddings API(H1 死循环双根因)。
|
|
163
|
+
*/
|
|
164
|
+
getL1ForReindex(exclude?: Set<string>): Array<{
|
|
110
165
|
id: string;
|
|
111
166
|
content: string;
|
|
112
167
|
}>;
|
|
113
|
-
|
|
168
|
+
/** 待重嵌入的 L0(增量 + 排除 skip 集,同 getL1ForReindex)。 */
|
|
169
|
+
getL0ForReindex(exclude?: Set<string>): Array<{
|
|
114
170
|
id: string;
|
|
115
171
|
text: string;
|
|
116
172
|
}>;
|
|
173
|
+
getVecSkipSet(kind: 'l1' | 'l0'): Set<string>;
|
|
174
|
+
addVecSkippedIds(kind: 'l1' | 'l0', ids: string[]): void;
|
|
175
|
+
clearVecSkipIds(kind: 'l1' | 'l0'): void;
|
|
117
176
|
/** 只更新向量行(重嵌入用)。 */
|
|
118
177
|
updateL1Vec(id: string, embedding: Float32Array): boolean;
|
|
119
178
|
updateL0Vec(id: string, embedding: Float32Array, recordedAt: string): boolean;
|
|
120
179
|
close(): void;
|
|
121
180
|
}
|
|
181
|
+
/** 全零向量(cosine 未定义,不可入向量表)。reindex 侧用它区分"不可嵌入"与"写入失败"。 */
|
|
182
|
+
export declare function isZeroVector(vec: Float32Array): boolean;
|