dsh-prime-memory 0.11.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.
Files changed (151) hide show
  1. package/CHANGELOG.en.md +28 -0
  2. package/CHANGELOG.ja.md +30 -0
  3. package/CHANGELOG.ko.md +30 -0
  4. package/CHANGELOG.md +1220 -0
  5. package/ENGINEERING-NOTES.md +452 -0
  6. package/INSTALL.en.md +92 -0
  7. package/INSTALL.ja.md +92 -0
  8. package/INSTALL.ko.md +92 -0
  9. package/INSTALL.md +92 -0
  10. package/LICENSE +21 -0
  11. package/README.en.md +458 -0
  12. package/README.ja.md +306 -0
  13. package/README.ko.md +306 -0
  14. package/README.md +425 -0
  15. package/assets/changelog/0.8.10/01-write-only-pill.png +0 -0
  16. package/assets/changelog/0.8.9/01-panel.png +0 -0
  17. package/assets/changelog/0.8.9/02-halo.png +0 -0
  18. package/assets/changelog/0.8.9/03-layer-segmented-panel.png +0 -0
  19. package/assets/changelog/0.8.9/04-layer-l1-panel.png +0 -0
  20. package/assets/img/EmbeddingSource.png +0 -0
  21. package/assets/img/Hero.png +0 -0
  22. package/assets/img/Layers.png +0 -0
  23. package/assets/img/MemoryTools.png +0 -0
  24. package/assets/img/Modes.png +0 -0
  25. package/assets/img/ToolTrajectory.png +0 -0
  26. package/assets/img/ui-dark.jpg +0 -0
  27. package/assets/img/ui-light.jpg +0 -0
  28. package/assets/readme/bench-dialog.svg +70 -0
  29. package/assets/readme/bench-workflow.svg +79 -0
  30. package/assets/readme/flow.svg +189 -0
  31. package/assets/readme/storage.svg +115 -0
  32. package/cordis.patch.yml +16 -0
  33. package/dist/bench-control.d.ts +34 -0
  34. package/dist/bench-control.js +16 -0
  35. package/dist/client.js +4293 -0
  36. package/dist/config.d.ts +683 -0
  37. package/dist/config.js +129 -0
  38. package/dist/contract.d.ts +820 -0
  39. package/dist/contract.js +1 -0
  40. package/dist/embedding-worker.cjs +176 -0
  41. package/dist/graph/apply.d.ts +37 -0
  42. package/dist/graph/apply.js +270 -0
  43. package/dist/graph/constraints.d.ts +47 -0
  44. package/dist/graph/constraints.js +38 -0
  45. package/dist/graph/search.d.ts +16 -0
  46. package/dist/graph/search.js +115 -0
  47. package/dist/graph/types.d.ts +142 -0
  48. package/dist/graph/types.js +14 -0
  49. package/dist/hooks/capture.d.ts +32 -0
  50. package/dist/hooks/capture.js +194 -0
  51. package/dist/hooks/recall.d.ts +63 -0
  52. package/dist/hooks/recall.js +429 -0
  53. package/dist/index.d.ts +534 -0
  54. package/dist/index.js +344 -0
  55. package/dist/llm-usage.d.ts +26 -0
  56. package/dist/llm-usage.js +37 -0
  57. package/dist/llm.d.ts +153 -0
  58. package/dist/llm.js +530 -0
  59. package/dist/pipeline/graph.d.ts +35 -0
  60. package/dist/pipeline/graph.js +104 -0
  61. package/dist/pipeline/l1.d.ts +19 -0
  62. package/dist/pipeline/l1.js +271 -0
  63. package/dist/pipeline/l2.d.ts +13 -0
  64. package/dist/pipeline/l2.js +83 -0
  65. package/dist/pipeline/l3.d.ts +15 -0
  66. package/dist/pipeline/l3.js +78 -0
  67. package/dist/pipeline/rebuild.d.ts +61 -0
  68. package/dist/pipeline/rebuild.js +307 -0
  69. package/dist/pipeline/ruminate.d.ts +89 -0
  70. package/dist/pipeline/ruminate.js +298 -0
  71. package/dist/pipeline/runner.d.ts +167 -0
  72. package/dist/pipeline/runner.js +638 -0
  73. package/dist/pipeline/trigger.d.ts +40 -0
  74. package/dist/pipeline/trigger.js +75 -0
  75. package/dist/prompts/graph-projection.d.ts +70 -0
  76. package/dist/prompts/graph-projection.js +167 -0
  77. package/dist/prompts/l1-dedup.d.ts +22 -0
  78. package/dist/prompts/l1-dedup.js +251 -0
  79. package/dist/prompts/l1-extraction.d.ts +22 -0
  80. package/dist/prompts/l1-extraction.js +457 -0
  81. package/dist/prompts/persona.d.ts +23 -0
  82. package/dist/prompts/persona.js +240 -0
  83. package/dist/prompts/scene.d.ts +32 -0
  84. package/dist/prompts/scene.js +414 -0
  85. package/dist/runtime-package-lock.json +982 -0
  86. package/dist/settings.d.ts +50 -0
  87. package/dist/settings.js +355 -0
  88. package/dist/stats.d.ts +109 -0
  89. package/dist/stats.js +929 -0
  90. package/dist/store/bm25.d.ts +19 -0
  91. package/dist/store/bm25.js +63 -0
  92. package/dist/store/cost-ledger.d.ts +75 -0
  93. package/dist/store/cost-ledger.js +171 -0
  94. package/dist/store/download-queue.d.ts +79 -0
  95. package/dist/store/download-queue.js +424 -0
  96. package/dist/store/embedding-source.d.ts +118 -0
  97. package/dist/store/embedding-source.js +443 -0
  98. package/dist/store/embedding.d.ts +90 -0
  99. package/dist/store/embedding.js +206 -0
  100. package/dist/store/graph-store.d.ts +94 -0
  101. package/dist/store/graph-store.js +641 -0
  102. package/dist/store/l0.d.ts +40 -0
  103. package/dist/store/l0.js +197 -0
  104. package/dist/store/l1.d.ts +93 -0
  105. package/dist/store/l1.js +297 -0
  106. package/dist/store/local-embedding.d.ts +89 -0
  107. package/dist/store/local-embedding.js +227 -0
  108. package/dist/store/model-catalog.d.ts +48 -0
  109. package/dist/store/model-catalog.js +81 -0
  110. package/dist/store/occupancy.d.ts +30 -0
  111. package/dist/store/occupancy.js +134 -0
  112. package/dist/store/pending.d.ts +36 -0
  113. package/dist/store/pending.js +103 -0
  114. package/dist/store/persona.d.ts +15 -0
  115. package/dist/store/persona.js +60 -0
  116. package/dist/store/recall-dedupe.d.ts +26 -0
  117. package/dist/store/recall-dedupe.js +138 -0
  118. package/dist/store/runtime-installer.d.ts +59 -0
  119. package/dist/store/runtime-installer.js +243 -0
  120. package/dist/store/scenes.d.ts +24 -0
  121. package/dist/store/scenes.js +160 -0
  122. package/dist/store/search-utils.d.ts +38 -0
  123. package/dist/store/search-utils.js +100 -0
  124. package/dist/store/session-modes.d.ts +35 -0
  125. package/dist/store/session-modes.js +144 -0
  126. package/dist/store/sqlite.d.ts +246 -0
  127. package/dist/store/sqlite.js +1491 -0
  128. package/dist/store/state.d.ts +41 -0
  129. package/dist/store/state.js +72 -0
  130. package/dist/token-cost.d.ts +23 -0
  131. package/dist/token-cost.js +185 -0
  132. package/dist/tools/index.d.ts +34 -0
  133. package/dist/tools/index.js +758 -0
  134. package/dist/types.d.ts +139 -0
  135. package/dist/types.js +38 -0
  136. package/dist/util/context-occupancy.d.ts +68 -0
  137. package/dist/util/context-occupancy.js +92 -0
  138. package/dist/util/filelog.d.ts +6 -0
  139. package/dist/util/filelog.js +108 -0
  140. package/dist/util/io.d.ts +18 -0
  141. package/dist/util/io.js +97 -0
  142. package/dist/util/recall-budget.d.ts +31 -0
  143. package/dist/util/recall-budget.js +84 -0
  144. package/dist/util/sanitize.d.ts +11 -0
  145. package/dist/util/sanitize.js +67 -0
  146. package/dist/util/text.d.ts +16 -0
  147. package/dist/util/text.js +61 -0
  148. package/dist/util/tokenizer.d.ts +9 -0
  149. package/dist/util/tokenizer.js +50 -0
  150. package/dsh.plugin.json +22 -0
  151. package/package.json +118 -0
@@ -0,0 +1,206 @@
1
+ /** 空实现:向量能力关闭/不可用时使用,一切调用安全返回。 */
2
+ export class NoopEmbeddingService {
3
+ getDimensions() {
4
+ return 0;
5
+ }
6
+ getProviderInfo() {
7
+ return { provider: 'noop', model: 'disabled', dimensions: 0 };
8
+ }
9
+ isReady() {
10
+ return false;
11
+ }
12
+ async embed() {
13
+ return new Float32Array(0);
14
+ }
15
+ async embedBatch(texts) {
16
+ return texts.map(() => new Float32Array(0));
17
+ }
18
+ }
19
+ /** 计数信号量(嵌入调用并发受限;防批量抽取候选召回打爆上游)。 */
20
+ class Semaphore {
21
+ limit;
22
+ active = 0;
23
+ waiters = [];
24
+ constructor(limit) {
25
+ this.limit = limit;
26
+ }
27
+ async acquire() {
28
+ if (this.active < this.limit) {
29
+ this.active++;
30
+ return () => this.release();
31
+ }
32
+ return new Promise((resolve) => {
33
+ this.waiters.push(() => {
34
+ this.active++;
35
+ resolve(() => this.release());
36
+ });
37
+ });
38
+ }
39
+ release() {
40
+ this.active--;
41
+ this.waiters.shift()?.();
42
+ }
43
+ }
44
+ function sleep(ms) {
45
+ return new Promise((r) => setTimeout(r, ms));
46
+ }
47
+ /** 判定是否值得重试:网络层错误 / 5xx / 429;4xx 参数类错误立即失败。 */
48
+ function isRetryable(err) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ if (/HTTP 5\d\d|HTTP 429|fetch failed|terminated|ECONN|ETIMEDOUT|abort/i.test(msg))
51
+ return true;
52
+ return !/HTTP 4\d\d/.test(msg) && !/不匹配/.test(msg);
53
+ }
54
+ /**
55
+ * OpenAI 兼容远程 embedding:POST {baseUrl}/embeddings。
56
+ * 向量在客户端做 L2 归一化(与余弦度量配套)。
57
+ */
58
+ export class RemoteEmbeddingService {
59
+ opts;
60
+ sem;
61
+ constructor(opts) {
62
+ this.opts = { maxInputChars: 5000, timeoutMs: 10_000, maxRetries: 2, concurrency: 4, ...opts };
63
+ this.sem = new Semaphore(Math.max(1, this.opts.concurrency));
64
+ }
65
+ getDimensions() {
66
+ return this.opts.dimensions;
67
+ }
68
+ getProviderInfo() {
69
+ return { provider: 'remote', model: this.opts.model, dimensions: this.opts.dimensions };
70
+ }
71
+ isReady() {
72
+ return true;
73
+ }
74
+ async embed(text, callOpts) {
75
+ const [vec] = await this.embedBatch([text], callOpts);
76
+ return vec;
77
+ }
78
+ async embedBatch(texts, callOpts) {
79
+ if (texts.length === 0)
80
+ return [];
81
+ const release = await this.sem.acquire();
82
+ try {
83
+ return await this.callWithRetry(texts, callOpts);
84
+ }
85
+ finally {
86
+ release();
87
+ }
88
+ }
89
+ async callWithRetry(texts, callOpts) {
90
+ const maxRetries = Math.max(0, this.opts.maxRetries);
91
+ let lastErr;
92
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
93
+ try {
94
+ return await this.callOnce(texts, callOpts);
95
+ }
96
+ catch (err) {
97
+ lastErr = err;
98
+ if (attempt === maxRetries || !isRetryable(err))
99
+ break;
100
+ // 指数退避带抖动:250ms → 500ms → …,±25% 抖动防同刻重试踩踏
101
+ const base = 250 * 2 ** attempt;
102
+ await sleep(Math.round(base * (0.75 + Math.random() * 0.5)));
103
+ }
104
+ }
105
+ throw lastErr;
106
+ }
107
+ async callOnce(texts, callOpts) {
108
+ const input = texts.map((t) => t.slice(0, this.opts.maxInputChars));
109
+ const base = this.opts.baseUrl.replace(/\/+$/, '');
110
+ // 内层钳制只缩短不放大:召回路径传短超时,给 FTS 降级留时间
111
+ const timeoutMs = Math.min(this.opts.timeoutMs, callOpts?.timeoutMs ?? this.opts.timeoutMs);
112
+ const headers = { 'content-type': 'application/json' };
113
+ // 免 key 自托管不设鉴权头(空 Bearer 会被部分服务拒绝)
114
+ if (this.opts.apiKey)
115
+ headers.authorization = `Bearer ${this.opts.apiKey}`;
116
+ const res = await fetch(`${base}/embeddings`, {
117
+ method: 'POST',
118
+ headers,
119
+ body: JSON.stringify({ model: this.opts.model, input }),
120
+ signal: AbortSignal.timeout(timeoutMs),
121
+ });
122
+ if (!res.ok) {
123
+ const body = await res.text().catch(() => '');
124
+ throw new Error(`embeddings HTTP ${res.status}: ${body.slice(0, 200)}`);
125
+ }
126
+ const json = (await res.json());
127
+ const data = json.data ?? [];
128
+ if (data.length !== texts.length) {
129
+ throw new Error(`embeddings 返回数量不匹配:期望 ${texts.length},得到 ${data.length}`);
130
+ }
131
+ // 按 index 还原顺序(部分服务商不保序)
132
+ const byIndex = new Map();
133
+ for (const d of data)
134
+ byIndex.set(d.index, d.embedding);
135
+ return input.map((_, i) => {
136
+ const raw = byIndex.get(i);
137
+ if (!raw || raw.length !== this.opts.dimensions) {
138
+ throw new Error(`embedding 维度不匹配:期望 ${this.opts.dimensions},得到 ${raw?.length ?? 0}`);
139
+ }
140
+ return sanitizeAndNormalize(raw);
141
+ });
142
+ }
143
+ }
144
+ /** 清洗 NaN/Inf 并 L2 归一化(零向量原样返回,写入侧会跳过)。 */
145
+ function sanitizeAndNormalize(vec) {
146
+ const arr = Array.from(vec).map((v) => (Number.isFinite(v) ? v : 0));
147
+ const magnitude = Math.sqrt(arr.reduce((sum, v) => sum + v * v, 0));
148
+ if (magnitude < 1e-10)
149
+ return new Float32Array(arr);
150
+ return new Float32Array(arr.map((v) => v / magnitude));
151
+ }
152
+ /**
153
+ * 嵌入调用降级助手(L0/L1 Store 共用):查询向量失败 → undefined(调用方降级 FTS);
154
+ * 批量嵌入失败 → 全 undefined(跳过向量写入,由周期性 backfill 补齐)。
155
+ * 同类失败只告警一次,避免刷屏。
156
+ */
157
+ export class EmbedHelper {
158
+ embed;
159
+ logger;
160
+ warned = false;
161
+ constructor(embed, logger) {
162
+ this.embed = embed;
163
+ this.logger = logger;
164
+ }
165
+ /** 活切换嵌入源:换掉底层服务并复位一次性告警(新服务重新获得告警机会)。 */
166
+ setService(svc) {
167
+ this.embed = svc;
168
+ this.warned = false;
169
+ }
170
+ vectorReady() {
171
+ return this.embed.isReady();
172
+ }
173
+ /** 查询向量;失败或空向量返回 undefined(调用方降级 FTS)。
174
+ * timeoutMs 为内层钳制(仅缩短服务超时),召回路径使用。 */
175
+ async query(text, timeoutMs) {
176
+ try {
177
+ const vec = await this.embed.embed(text, timeoutMs != null ? { timeoutMs } : undefined);
178
+ return vec.length > 0 ? vec : undefined;
179
+ }
180
+ catch (err) {
181
+ this.warn(`查询向量计算失败,降级 FTS: ${errMsg(err)}`);
182
+ return undefined;
183
+ }
184
+ }
185
+ /** 批量嵌入;服务未就绪或失败时返回全 undefined(不阻断元数据/FTS 写入)。 */
186
+ async batch(texts) {
187
+ if (!this.embed.isReady())
188
+ return texts.map(() => undefined);
189
+ try {
190
+ return await this.embed.embedBatch(texts);
191
+ }
192
+ catch (err) {
193
+ this.warn(`批量嵌入失败,本批暂不写向量(后台 backfill 会补齐): ${errMsg(err)}`);
194
+ return texts.map(() => undefined);
195
+ }
196
+ }
197
+ warn(msg) {
198
+ if (this.warned)
199
+ return;
200
+ this.warned = true;
201
+ this.logger?.warn(`[memory] ${msg}`);
202
+ }
203
+ }
204
+ function errMsg(err) {
205
+ return err instanceof Error ? err.message : String(err);
206
+ }
@@ -0,0 +1,94 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { GraphEdge, GraphNode, GraphNodeSearchResult, GraphProjectionJob, GraphProjectionResult } from '../graph/types.js';
3
+ import type { MemoryFamily, MemoryLogger, MemoryRecord } from '../types.js';
4
+ /** claim 的产出:job 元数据 + 本批真实存在的来源记录(已剔除被删者)。 */
5
+ export interface GraphClaim {
6
+ job: GraphProjectionJob;
7
+ records: MemoryRecord[];
8
+ }
9
+ /** complete 的注入缝(测试回滚路径 / 确定性断言用)。 */
10
+ export interface CompleteOptions {
11
+ now?: string;
12
+ idFactory?: (prefix: 'node' | 'edge' | 'gfact') => string;
13
+ }
14
+ export declare class GraphStore {
15
+ private db;
16
+ private logger;
17
+ private stmtInsertNode;
18
+ private stmtInsertEdge;
19
+ /** init 是否就绪(未就绪 = 图谱域整体 no-op,不抛错不传染)。 */
20
+ get ready(): boolean;
21
+ /**
22
+ * 建表 + 语句缓存(MemoryDb.initSchema 内调用)。任何一步失败都只告警并保持
23
+ * 未就绪——图谱域整体降级 no-op,检索主链路(L0/L1/FTS/向量)不受影响。
24
+ */
25
+ init(db: DatabaseSync, logger?: MemoryLogger): void;
26
+ /** 插件停机时清空连接引用(dispose 序调用,防悬空引用)。 */
27
+ close(): void;
28
+ /** 统一事务边界(immediate 供 complete 全程持写锁)。 */
29
+ private tx;
30
+ /** 事务内的 upsert 体(upsertTouched 与 complete 共用;调用方负责事务)。 */
31
+ private upsertTouchedInTx;
32
+ /** 全量读图谱(apply scope 与检索的统一入口;图谱量级为可重建投影,百~千级)。 */
33
+ loadGraph(): {
34
+ nodes: GraphNode[];
35
+ edges: GraphEdge[];
36
+ };
37
+ /** 事务内的全图读取(complete 用;调用方负责事务)。 */
38
+ private loadGraphInTx;
39
+ /** 事务内按 id 装载 L1 记录(claim 与 complete 共用;调用方负责事务)。 */
40
+ private loadRecordsInTx;
41
+ /** 单节点详情(expand 用;不存在/未就绪返回 null)。 */
42
+ getNode(id: string): GraphNode | null;
43
+ /** 与某节点相连的 active 边(expand 用;悬挂 id 返回空数组,不解析不抛)。 */
44
+ edgesOf(nodeId: string): GraphEdge[];
45
+ /**
46
+ * 图谱检索(纯函数 searchGraphNodes 的存储缝;可选族过滤)。
47
+ * families 非空时只返回本族衍生的节点(档位隔离;无族信息节点一律不可见——
48
+ * 宁可漏不可串)。降级/异常返回空数组。
49
+ */
50
+ searchNodes(query: string, limit: number, families?: readonly MemoryFamily[]): GraphNodeSearchResult[];
51
+ private newJobId;
52
+ /**
53
+ * 投影入队(按 GRAPH_JOB_BATCH 分片成多个 job;去重下推 SQL):
54
+ * 已有在途 mapping(pending/running/failed 退避中)或已按当前版本完成投影的
55
+ * 记录跳过。返回实际新建的 job 数。
56
+ */
57
+ queueGraphProjection(recordIds: readonly string[], priority: number): number;
58
+ /**
59
+ * 取下一个可执行 job 并置 running(attempts +1):只认当前 projectorVersion,
60
+ * attempts 封顶与退避窗口在 WHERE 里过滤;来源全缺失 → job 判 dead 返 null
61
+ * (不可重试不抛)。部分缺失时 job 收缩到真实存在的记录子集。
62
+ */
63
+ claimNext(): GraphClaim | null;
64
+ /**
65
+ * 提交投影结果(单事务,BEGIN IMMEDIATE):读全图 scope → 纯函数 apply(硬
66
+ * 校验)→ 写回 touched 行 → job 置 completed + 登记已投影记录 + 放掉 mapping。
67
+ * job 非 running 态(已完成/已 dead/已回收)一律幂等 no-op;任何一步抛错整体
68
+ * 回滚,不留半写。
69
+ */
70
+ complete(jobId: string, result: GraphProjectionResult, opts?: CompleteOptions): void;
71
+ /**
72
+ * 投影失败收尾:attempts 已在 claim 时 +1——封顶转 dead(放掉 mapping,允许
73
+ * 重新入队);未封顶转 failed + 指数退避 nextAttemptAt(mapping 保留防重复入队)。
74
+ */
75
+ fail(jobId: string, error: string): void;
76
+ /** 启动回收:上次进程退出时卡在 running 的 job 放回 pending(dispose 缝不永久卡批)。 */
77
+ recoverRunning(): number;
78
+ /** 最近 job 列表(诊断/面板用;未就绪返回空)。 */
79
+ listJobs(limit?: number): GraphProjectionJob[];
80
+ /**
81
+ * 存量补投影:从未投影(当前版本)且无在途 mapping 的 L1 记录里按创建时间
82
+ * 升序取最多 limit 条分片入队(优先级 GRAPH_PRIORITY_BACKFILL,恒低于新蒸馏)。
83
+ * 返回新建 job 数。
84
+ */
85
+ queueMissing(limit: number): number;
86
+ /**
87
+ * 删除传播(L1 批量删除后的惰性墓碑):来源在 L1 已全部不存在的 active/disputed
88
+ * 节点与边标 archived(保留行,expand 可见墓碑)。挂在 MemoryDb.deleteL1Batch
89
+ * 之后;按 L1 存活集判定(而非本次删除集合),跨多次删除与历史孤儿一并收敛。
90
+ */
91
+ markSourcesDeleted(deletedIds: readonly string[]): void;
92
+ /** 清空全部图谱数据(L1 重建时调用——图谱是 L1 的投影,记录清空即图谱作废)。 */
93
+ resetAll(): void;
94
+ }