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,298 @@
1
+ import { groupPendingBySession, loadPending, } from '../store/pending.js';
2
+ import { errDetail } from '../util/filelog.js';
3
+ import { runSceneConsolidation } from './l2.js';
4
+ import { runPersona } from './l3.js';
5
+ const IDLE_STATUS = {
6
+ running: false,
7
+ phase: 'idle',
8
+ done: 0,
9
+ total: 0,
10
+ recordsBuilt: 0,
11
+ detail: null,
12
+ cancelRequested: false,
13
+ startedAt: null,
14
+ finishedAt: null,
15
+ error: null,
16
+ };
17
+ function groupSessions(buckets) {
18
+ const sessions = [];
19
+ for (const mode of ['auto', 'chat', 'work']) {
20
+ const groups = groupPendingBySession(buckets[mode]);
21
+ for (const g of groups) {
22
+ if (g.messages.length === 0)
23
+ continue;
24
+ sessions.push({ sessionId: g.sessionId, mode, messages: g.messages });
25
+ }
26
+ }
27
+ return sessions;
28
+ }
29
+ export class RuminateController {
30
+ ctx;
31
+ cfg;
32
+ runner;
33
+ stores;
34
+ logger;
35
+ live;
36
+ status = IDLE_STATUS;
37
+ cancelRequested = false;
38
+ /** 启动守卫:置位早于 status.running,封住"await 读取"期间的双击窗口。 */
39
+ starting = false;
40
+ sessions = [];
41
+ totalL1 = 0;
42
+ pendingFile;
43
+ constructor(ctx, cfg, runner, stores, logger, live, pendingFile) {
44
+ this.ctx = ctx;
45
+ this.cfg = cfg;
46
+ this.runner = runner;
47
+ this.stores = stores;
48
+ this.logger = logger;
49
+ this.live = live;
50
+ this.pendingFile = pendingFile;
51
+ }
52
+ /** 状态快照 */
53
+ getStatus() {
54
+ return { ...this.status };
55
+ }
56
+ /**
57
+ * 启动反刍:扫描 pending 三桶,按会话分组,逐个入队蒸馏;完成后强制 L2+L3。
58
+ * 桶形状的唯一权威是 store/pending.ts 的 loadPending——不得在本层再解析该文件。
59
+ */
60
+ async start() {
61
+ if (this.status.running || this.starting)
62
+ throw new Error('反刍已在进行中');
63
+ this.starting = true;
64
+ try {
65
+ const liveNow = this.live.get();
66
+ if (!liveNow.enabled || !liveNow.distill) {
67
+ throw new Error('蒸馏开关已关闭,请先开启蒸馏再反刍');
68
+ }
69
+ if (!this.cfg.extract.enabled) {
70
+ throw new Error('部署配置已停用蒸馏(extract.enabled=false),无法反刍');
71
+ }
72
+ // 读取 pending 三桶(loadPending 负责解包 PendingFile 并校验条目)
73
+ const { buckets } = await loadPending(this.pendingFile, this.logger);
74
+ this.sessions = groupSessions(buckets);
75
+ this.cancelRequested = false;
76
+ this.totalL1 = 0;
77
+ if (this.sessions.length === 0) {
78
+ // 没有 pending 切片 → 做 L2/L3 轻量刷新
79
+ return await this.doLightRefresh();
80
+ }
81
+ this.status = {
82
+ ...IDLE_STATUS,
83
+ running: true,
84
+ phase: 'distilling',
85
+ total: this.sessions.length,
86
+ startedAt: Date.now(),
87
+ };
88
+ this.logger.info(`[memory] 反刍开始:${this.sessions.length} 个会话待消化`);
89
+ // 第一个会话入队
90
+ this.doEnqueue(0);
91
+ // 此刻 status.running 已置位,后续并发 start() 由该守卫拦截
92
+ this.starting = false;
93
+ return { ...this.status };
94
+ }
95
+ catch (err) {
96
+ // 失败必须落状态:否则 status 仍报 idle/error:null,与面板上的报错自相矛盾
97
+ this.status = {
98
+ ...IDLE_STATUS,
99
+ running: false,
100
+ phase: 'failed',
101
+ error: errDetail(err),
102
+ finishedAt: Date.now(),
103
+ };
104
+ this.logger.warn(`[memory] 反刍启动失败: ${errDetail(err)}`);
105
+ throw err;
106
+ }
107
+ finally {
108
+ this.starting = false;
109
+ }
110
+ }
111
+ /** 请求取消:当前块完成后停止,已蒸馏部分保留。 */
112
+ requestCancel() {
113
+ if (!this.status.running)
114
+ return this.getStatus();
115
+ this.cancelRequested = true;
116
+ this.status.cancelRequested = true;
117
+ this.logger.info('[memory] 反刍取消已请求(当前块完成后停止)');
118
+ return { ...this.status };
119
+ }
120
+ /** 入队一个会话。 */
121
+ doEnqueue(index) {
122
+ if (this.cancelRequested || index >= this.sessions.length) {
123
+ // 已登记的会话(含被取消截断者)视为已完成,避免 done 恒 0
124
+ this.status.done = index;
125
+ // 收尾
126
+ void this.finalize();
127
+ return;
128
+ }
129
+ const session = this.sessions[index];
130
+ this.status.phase = 'distilling';
131
+ this.status.detail = `L1 蒸馏中:会话 ${session.sessionId}(第 ${index + 1}/${this.sessions.length} 个)`;
132
+ // 真实产出计数:enqueue 只入队,产出由 onTurnDone 在任务真正跑完后回调
133
+ // (此前 totalL1 从不累加,recordsBuilt 与完成日志恒为 0,修复效果无法判定)
134
+ this.runner.enqueue(session.sessionId, session.messages, session.mode, {
135
+ force: true,
136
+ onTurnDone: (records) => {
137
+ this.totalL1 += records;
138
+ this.status.recordsBuilt = this.totalL1;
139
+ },
140
+ });
141
+ // runner 的 drain 是同步循环,给一个 tick 让它消费完再入队下一个
142
+ setImmediate(() => this.doEnqueue(index + 1));
143
+ }
144
+ /** 收尾:强制 L2 + L3。 */
145
+ async finalize() {
146
+ try {
147
+ this.status.phase = 'consolidating';
148
+ this.status.detail = 'L2 场景整合(收尾)';
149
+ const liveNow = this.live.get();
150
+ const distillOn = liveNow.enabled && liveNow.distill;
151
+ // 强制 L2:把全部未整合的残余记录落进场景
152
+ if (this.cfg.l2.enabled && distillOn) {
153
+ for (const family of ['chat', 'work']) {
154
+ const fstate = this.runner.states?.[family];
155
+ if (!fstate)
156
+ continue;
157
+ // 收集本族所有记录(反刍后全部未整合)
158
+ const records = this.stores.l1.list({ family, limit: 500, offset: 0 }).items;
159
+ if (records.length === 0)
160
+ continue;
161
+ try {
162
+ const t = Date.now();
163
+ await runSceneConsolidation(this.ctx, this.cfg, this.stores.scenes[family], records, this.logger, family);
164
+ if (fstate) {
165
+ fstate.newMemoriesSinceL2 = 0;
166
+ fstate.lastL2At = Date.now();
167
+ }
168
+ this.logger.info(`[memory] 反刍 L2 完成(family=${family},${Date.now() - t}ms,${records.length} 条记录)`);
169
+ }
170
+ catch (err) {
171
+ this.logger.warn(`[memory] 反刍 L2 失败(family=${family}): ${errDetail(err)}`);
172
+ }
173
+ }
174
+ }
175
+ // 强制 L3:画像更新
176
+ if (this.cfg.l3.enabled && distillOn) {
177
+ this.status.phase = 'updating';
178
+ this.status.detail = 'L3 画像更新';
179
+ for (const family of ['chat', 'work']) {
180
+ try {
181
+ const scenes = await this.stores.scenes[family].list();
182
+ if (scenes.length === 0)
183
+ continue;
184
+ const fstate = this.runner.states?.[family];
185
+ await runPersona(this.ctx, this.cfg, this.stores.scenes[family], this.stores.persona[family], fstate || undefined, this.logger, family);
186
+ this.logger.info(`[memory] 反刍 L3 完成(family=${family})`);
187
+ }
188
+ catch (err) {
189
+ this.logger.warn(`[memory] 反刍 L3 失败(family=${family}): ${errDetail(err)}`);
190
+ }
191
+ }
192
+ }
193
+ this.status.phase = 'done';
194
+ this.finish(null);
195
+ }
196
+ catch (err) {
197
+ this.finish(`收尾失败: ${errDetail(err)}`);
198
+ }
199
+ }
200
+ /**
201
+ * 轻量刷新:没有 pending 时仅跑一轮 L2/L3。
202
+ *
203
+ * 为什么必须先置 running/phase:此处每次 runSceneConsolidation/runPersona 都是**真实 LLM 调用**
204
+ * (实测单次可达 70s+),而本方法是 `await` 的、期间 `start()` 一直挂着并占住守卫。
205
+ * 原先不置 running,导致刷新期间界面既无进度也无取消按钮,再点一次只得到"反刍已在进行中"
206
+ * 却看不到任何进展——即用户反馈的"管线进度和状态提示仍然欠缺"。
207
+ * 现在:先把步骤数登记为 total、每完成一步自增 done,并给出当前动作描述(detail)。
208
+ */
209
+ async doLightRefresh() {
210
+ const liveNow = this.live.get();
211
+ const distillOn = liveNow.enabled && liveNow.distill;
212
+ // 先算出待执行步骤(每族 L2/L3 各算一步),据此给界面一个真实进度
213
+ const l2Targets = [];
214
+ if (this.cfg.l2.enabled && distillOn) {
215
+ for (const family of ['chat', 'work']) {
216
+ const fstate = this.runner.states?.[family];
217
+ if (!fstate || fstate.newMemoriesSinceL2 <= 0)
218
+ continue;
219
+ const records = this.stores.l1.list({ family, limit: 200, offset: 0 }).items;
220
+ if (records.length === 0)
221
+ continue;
222
+ l2Targets.push(family);
223
+ }
224
+ }
225
+ const l3Targets = [];
226
+ if (this.cfg.l3.enabled && distillOn) {
227
+ for (const family of ['chat', 'work']) {
228
+ if (!this.runner.states?.[family])
229
+ continue;
230
+ l3Targets.push(family);
231
+ }
232
+ }
233
+ this.cancelRequested = false;
234
+ this.status = {
235
+ ...IDLE_STATUS,
236
+ running: true,
237
+ phase: 'refreshing',
238
+ done: 0,
239
+ total: l2Targets.length + l3Targets.length,
240
+ detail: '准备轻量刷新(无未蒸馏缓冲)',
241
+ startedAt: Date.now(),
242
+ };
243
+ this.logger.info(`[memory] 反刍轻量刷新开始(pending 无切片):L2 ${l2Targets.length} 族 / L3 ${l3Targets.length} 族`);
244
+ if (l2Targets.length > 0)
245
+ this.status.phase = 'consolidating';
246
+ for (const family of l2Targets) {
247
+ if (this.cancelRequested)
248
+ break;
249
+ this.status.detail = `L2 场景整合(${family})`;
250
+ const fstate = this.runner.states?.[family];
251
+ const records = this.stores.l1.list({ family, limit: 200, offset: 0 }).items;
252
+ try {
253
+ await runSceneConsolidation(this.ctx, this.cfg, this.stores.scenes[family], records, this.logger, family);
254
+ // 仅成功后归零:失败时保留计数,留给下一次触发重试(原先先归零会静默吞掉失败)
255
+ if (fstate) {
256
+ fstate.newMemoriesSinceL2 = 0;
257
+ fstate.lastL2At = Date.now();
258
+ }
259
+ }
260
+ catch (err) {
261
+ this.logger.warn(`[memory] 反刍轻量刷新 L2 失败(family=${family}): ${errDetail(err)}`);
262
+ }
263
+ this.status.done++;
264
+ }
265
+ if (l3Targets.length > 0)
266
+ this.status.phase = 'updating';
267
+ for (const family of l3Targets) {
268
+ if (this.cancelRequested)
269
+ break;
270
+ this.status.detail = `L3 画像更新(${family})`;
271
+ const fstate = this.runner.states?.[family];
272
+ try {
273
+ await runPersona(this.ctx, this.cfg, this.stores.scenes[family], this.stores.persona[family], fstate, this.logger, family);
274
+ }
275
+ catch (err) {
276
+ this.logger.warn(`[memory] 反刍轻量刷新 L3 失败(family=${family}): ${errDetail(err)}`);
277
+ }
278
+ this.status.done++;
279
+ }
280
+ this.status.running = false;
281
+ this.status.detail = null;
282
+ this.status.phase = this.cancelRequested ? 'cancelled' : 'done';
283
+ this.status.finishedAt = Date.now();
284
+ const cost = this.status.finishedAt - (this.status.startedAt ?? this.status.finishedAt);
285
+ this.logger.info(`[memory] 反刍轻量刷新结束(${this.status.phase}):${this.status.done}/${this.status.total} 步,耗时 ${Math.round(cost / 1000)}s`);
286
+ return this.getStatus();
287
+ }
288
+ finish(error) {
289
+ this.status.running = false;
290
+ this.status.detail = null;
291
+ this.status.phase = this.cancelRequested ? 'cancelled' : 'done';
292
+ this.status.error = error;
293
+ this.status.finishedAt = Date.now();
294
+ this.status.recordsBuilt = this.totalL1;
295
+ const cost = this.status.finishedAt - (this.status.startedAt ?? this.status.finishedAt);
296
+ this.logger.info(`[memory] 反刍结束(${this.status.phase}):${this.status.done}/${this.status.total} 会话,产出 ${this.totalL1} 条记录,耗时 ${Math.round(cost / 1000)}s`);
297
+ }
298
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * 管线 Runner:串行执行 L0 → L1 → L2 → L3。
3
+ * 所有阶段失败只记日志,绝不向 Agent 循环抛错(失败兜底)。
4
+ *
5
+ * 会话档位:enqueue 带 mode(off 在捕获侧已被拦截);L1 待重试缓冲按档分桶;
6
+ * L2/L3 按记录族各自跑各自的场景/画像存储与阈值计数(分族隔离不变量)。
7
+ *
8
+ * 调度:内部是带优先级的任务列表——正常对话轮次(live)优先于重建分块(rebuild),
9
+ * 重建期间用户照常聊天,新轮次的蒸馏最多等一个重建块。任务串行,同一时刻至多一个在跑。
10
+ *
11
+ * 未蒸馏缓冲:pending 三桶持久化在 pending.json,进程重启不丢;init 恢复后延迟补跑一次
12
+ * (受 live 开关与 minMessages 阈值约束,失败维持"等下一轮同档对话"的现状语义)。
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import { type MemoryConfig } from '../config.js';
16
+ import type { LiveSettingsHandle } from '../settings.js';
17
+ import type { GraphStore } from '../store/graph-store.js';
18
+ import type { L0Store } from '../store/l0.js';
19
+ import type { L1Store } from '../store/l1.js';
20
+ import type { PersonaStore } from '../store/persona.js';
21
+ import type { SceneStore } from '../store/scenes.js';
22
+ import type { StateStore } from '../store/state.js';
23
+ import type { ConversationMessage, ExtractMode, MemoryFamily, MemoryLogger } from '../types.js';
24
+ import type { FamilyStates } from './l1.js';
25
+ export interface MemoryStores {
26
+ l0: L0Store;
27
+ l1: L1Store;
28
+ scenes: Record<MemoryFamily, SceneStore>;
29
+ persona: Record<MemoryFamily, PersonaStore>;
30
+ state: StateStore;
31
+ /** 图谱存储(可选:测试缝/未装配时图谱泵整体停用;GraphStore 自带降级 no-op)。 */
32
+ graph?: GraphStore;
33
+ }
34
+ /** 管线任务(优先级调度:live > graph > rebuild)。 */
35
+ export interface PipelineTask {
36
+ kind: 'live' | 'rebuild' | 'graph';
37
+ run: () => Promise<unknown>;
38
+ /** 任务跑完后的回调(携带 run 的产出)——供反刍统计真实产出记录数。 */
39
+ onDone?: (result: unknown) => void;
40
+ }
41
+ /**
42
+ * 选取下一个要执行的任务下标:最早的 live 优先,其次 graph(图谱投影单批短、
43
+ * 让位用户轮次但优先于重建分块),否则队首。
44
+ */
45
+ export declare function pickNextTaskIndex(tasks: PipelineTask[]): number;
46
+ /**
47
+ * 运行时调参视图:设置页运行时链(distillChain)与旧单路由/档位键、分层输出预算、
48
+ * 蒸馏通道覆盖、远程嵌入覆盖可临时覆盖静态 config。浅拷贝只覆盖 llm/embedding
49
+ * 相关键,其余键与原 cfg 共享只读引用;pipeline 全链继续收 cfg,无需感知。
50
+ *
51
+ * 优先级表(自上而下):
52
+ * - 蒸馏模型:部署静态 pin(provider+model 双字段齐)> 运行时统一链 distillChain
53
+ * [0]> 旧单路由键(distillProvider+distillModel 成对)> agentDefaultModel;
54
+ * pinned 时链与旧键一并失效(部署锁定路由)。
55
+ * - 思考档位:有 settings 服务时运行时值整体接管('' = 自动);链模式主路由档位走
56
+ * primaryEffort;非链模式旧键对静态回退条目盖章接管(存量兼容)。
57
+ * - 输出预算/输入预算/通道覆盖/远程嵌入覆盖:非零/非空运行时值直接注入对应子树。
58
+ */
59
+ export declare function effectiveCfg(cfg: MemoryConfig, live?: LiveSettingsHandle): MemoryConfig;
60
+ export declare class MemoryRunner {
61
+ private readonly ctx;
62
+ private readonly cfg;
63
+ private readonly stores;
64
+ private readonly logger;
65
+ private readonly live;
66
+ /** 会话档位只读句柄(闲置扫描跳过 off 档会话;挂起语义)。 */
67
+ private readonly modes?;
68
+ private tasks;
69
+ private draining;
70
+ /** 停止标志(dispose 序置位):不再取新任务;进行中任务自然收尾。 */
71
+ private stopped;
72
+ private pending;
73
+ /** 各档位桶渐进阈值(1 起步翻倍至稳态毕业;随 pending.json 持久化)。 */
74
+ private warmup;
75
+ /** 每会话最后活动时间(闲置兜底判定用)。 */
76
+ private lastActivity;
77
+ /** 每会话累计产出 L1 条数与最近蒸馏时间(session-stats 数据源;LRU 上限防泄漏)。 */
78
+ private sessionProduced;
79
+ /** 抽取连续失败退避(瞬态,不持久化:重启后允许首试再退避)。 */
80
+ private extractFailures;
81
+ /** 图谱泵在队列中的占位标志(同一时刻至多一个 graph 任务;护栏四件套之一)。 */
82
+ private graphPumpQueued;
83
+ private readonly pendingFile;
84
+ /** 分族 checkpoint(init 后可用;重建收尾也从这里读活引用)。 */
85
+ states: FamilyStates;
86
+ private afterRun;
87
+ constructor(ctx: Context, cfg: MemoryConfig, stores: MemoryStores, logger: MemoryLogger, live: LiveSettingsHandle,
88
+ /** 会话档位只读句柄(闲置扫描跳过 off 档会话;挂起语义)。 */
89
+ modes?: {
90
+ get(sessionId: string): string;
91
+ } | undefined);
92
+ init(): Promise<void>;
93
+ /** 启动补跑:对每个非空桶的每个会话切片入队一次蒸馏尝试(受 live 开关与
94
+ * 生效阈值约束;不足阈值的消息等用户继续或闲置兜底,失败不无限重试)。 */
95
+ private scheduleStartupRetry;
96
+ /** L1 抽取待重试的消息条数(状态面板用)。 */
97
+ get pendingCount(): number;
98
+ /**
99
+ * 会话级蒸馏视图(session-stats 端点数据源;纯内存读,零 I/O)。
100
+ * pendingSlice = 当前档位桶中该会话的攒批切片条数(threshold 为生效阈值,含 warmup 爬坡);
101
+ * parkedSlices = 其余档位桶中的残留切片(换档遗留 / off 档挂起)。
102
+ */
103
+ sessionView(sessionId: string, mode: string): {
104
+ pendingSlice: number;
105
+ parkedSlices: number;
106
+ threshold: number | null;
107
+ producedRecords: number;
108
+ lastDistillAt: number | null;
109
+ };
110
+ /** 会话产出记账(切片成功消费时调用;LRU 淘汰最久未蒸馏会话防 Map 无界增长)。 */
111
+ private noteSessionDistill;
112
+ /** 管线跑完一轮后的回调(用于召回缓存失效)。 */
113
+ setAfterRun(fn: () => void): void;
114
+ /** 一轮对话结束后入队(L0 落盘由 capture 在 turn/end 即时完成,不排蒸馏队列)。
115
+ * onTurnDone 在该任务真正跑完后回调,携带本轮新增记录数(供反刍统计真实产出)。 */
116
+ enqueue(sessionId: string, messages: ConversationMessage[], mode: ExtractMode, opts?: {
117
+ force?: boolean;
118
+ onTurnDone?: (records: number) => void;
119
+ }): void;
120
+ /** 重建任务入队(低优先级:让位于正常轮次;由 RebuildController 分块驱动)。 */
121
+ enqueueRebuildTask(run: () => Promise<unknown>): void;
122
+ /** 重建蒸馏轮:统一 auto 档,全量强制蒸馏(不受阈值约束)、不受缓冲 200 上限。 */
123
+ runRebuildTurn(sessionId: string, messages: ConversationMessage[]): Promise<number>;
124
+ /** 停止取新任务(插件 dispose 序调用;进行中任务照常跑完但不 await)。 */
125
+ stop(): void;
126
+ /** 启动闲置兜底定时器(index.ts 装配;idleSeconds=0 关闭)。 */
127
+ startIdleTimer(): void;
128
+ /** 闲置扫描:静默达标且有切片的会话按捕获档位落袋(off 档会话挂起跳过)。 */
129
+ private flushIdleSlices;
130
+ /** 把某会话在各桶中的切片按捕获档位逐个入队强制蒸馏(闲置兜底 / 档位切换共用)。 */
131
+ private enqueueSessionSlices;
132
+ /**
133
+ * 档位切换同步(session-modes 的 set() 回调,ADR-0003):
134
+ * 非 off 间切换 → 该会话切片立即按捕获档位蒸馏(新档位从空切片起步);
135
+ * 切到 off → 挂起(切片留存,闲置扫描跳过);从 off 切回 → 挂起片按捕获档位落袋。
136
+ */
137
+ onModeChange(sessionId: string, oldMode: string, newMode: string): void;
138
+ private pushTask;
139
+ /**
140
+ * 图谱泵任务入队(护栏四件套,见 maybeQueueGraphTask;成功投影后有积压则续排,
141
+ * 每轮 drain 至多消费一个 graph 任务且永远让位 live)。
142
+ */
143
+ startGraphBackfillTimer(): void;
144
+ /**
145
+ * 图谱投影泵入队判定:
146
+ * - 双门:部署级 cfg.graph.enabled + 运行时 live.enabled/distill,任一为假不入队;
147
+ * - 占位:同一时刻至多一个 graph 任务在队列(靠 graphPumpQueued 标志),永不与
148
+ * live 抢位(pickNextTaskIndex 保证 live > graph);
149
+ * - 退避/attempts 封顶:由 GraphStore.claimNext 的 WHERE 过滤与 fail 转 dead 承接,
150
+ * 退避窗口内的 claim 是廉价空转,不在此重复实现。
151
+ */
152
+ private maybeQueueGraphTask;
153
+ private drain;
154
+ /** 该会话是否处于抽取退避窗口内。 */
155
+ private inExtractBackoff;
156
+ /** 缓冲落盘(每次蒸馏尝试后调用;失败只告警不阻断管线)。
157
+ * 非重建轮持久化前按桶截断到上限:重建取消后的大桶不至于在后续每次
158
+ * 蒸馏尝试时反复整量序列化落盘(多 MB 级 IO);重建轮豁免维持。 */
159
+ private persistPending;
160
+ private runTurn;
161
+ /**
162
+ * 抽取并消费一个会话切片:成功才把切片移出桶并推进爬坡阈值;失败保留切片待重试。
163
+ * 调用方已保证切片达到生效阈值(或 force)。背景参考按会话从 L0 现查并剔除切片
164
+ * 自身(ADR-0003:会话间互不污染、重启不丢背景)。
165
+ */
166
+ private extractSessionSlice;
167
+ }