dsh-prime-memory 0.18.4 → 0.18.5
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 +28 -0
- package/dist/client.js +1297 -159
- package/dist/contract.d.ts +109 -1
- package/dist/hooks/capture.js +6 -2
- package/dist/hooks/recall.d.ts +4 -1
- package/dist/hooks/recall.js +10 -3
- package/dist/index.js +14 -4
- package/dist/metadata-validators.d.ts +5 -0
- package/dist/metadata-validators.js +11 -1
- package/dist/pipeline/ruminate.d.ts +11 -1
- package/dist/pipeline/ruminate.js +18 -1
- package/dist/room-review.d.ts +196 -0
- package/dist/room-review.js +281 -0
- package/dist/stats.d.ts +26 -2
- package/dist/stats.js +261 -13
- package/dist/store/l1.d.ts +34 -0
- package/dist/store/l1.js +68 -1
- package/dist/store/recall-usage.d.ts +43 -0
- package/dist/store/recall-usage.js +149 -0
- package/dist/store/rooms-registry.d.ts +66 -0
- package/dist/store/rooms-registry.js +250 -0
- package/dist/store/sqlite.d.ts +4 -0
- package/dist/store/sqlite.js +20 -0
- package/dist/tools/index.d.ts +4 -1
- package/dist/tools/index.js +224 -1
- package/dist/types.d.ts +4 -0
- package/package.json +13 -12
- package/dist/hall-backfill.d.ts +0 -33
- package/dist/hall-backfill.js +0 -95
- package/dist/hall-migrate.d.ts +0 -32
- package/dist/hall-migrate.js +0 -88
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { isTag } from './metadata-validators.js';
|
|
2
|
+
import { callLLM, parseJsonLogged } from './llm.js';
|
|
3
|
+
export const ROOM_CANDIDATES_KEY = 'roomCandidates';
|
|
4
|
+
export const ROOM_REVIEW_KEY = 'roomReview';
|
|
5
|
+
/** 解析标注器输出的逐条候选(容错:id 配对失败丢弃;isTag 校验;目录内优先,≤3)。 */
|
|
6
|
+
export function parseRoomCandidateItems(parsed, ids, roomCatalog) {
|
|
7
|
+
const idSet = new Set(ids);
|
|
8
|
+
const catalogSet = new Set(roomCatalog.map((r) => r.room));
|
|
9
|
+
const byId = new Map();
|
|
10
|
+
for (const item of parsed) {
|
|
11
|
+
if (!idSet.has(item.id))
|
|
12
|
+
continue;
|
|
13
|
+
const cleaned = [];
|
|
14
|
+
for (const r of item.rooms) {
|
|
15
|
+
const slug = typeof r === 'string' ? r.trim().toLowerCase() : '';
|
|
16
|
+
if (!slug || !isTag(slug))
|
|
17
|
+
continue;
|
|
18
|
+
if (!cleaned.includes(slug))
|
|
19
|
+
cleaned.push(slug);
|
|
20
|
+
}
|
|
21
|
+
// 目录内优先排前,其余兜底;截断到 3
|
|
22
|
+
cleaned.sort((a, b) => Number(catalogSet.has(b)) - Number(catalogSet.has(a)));
|
|
23
|
+
byId.set(item.id, cleaned.slice(0, 3));
|
|
24
|
+
}
|
|
25
|
+
return [...byId.entries()].map(([id, rooms]) => ({ id, rooms }));
|
|
26
|
+
}
|
|
27
|
+
/** 候选标注系统 prompt:喂现有 Room 词表(带计数),优先挂靠、少量新建兜底。 */
|
|
28
|
+
export function roomCandidateSystemPrompt(roomCatalog) {
|
|
29
|
+
const catalog = roomCatalog.length > 0
|
|
30
|
+
? roomCatalog
|
|
31
|
+
.slice(0, 40)
|
|
32
|
+
.map((r) => `- ${r.room}(现有 ${r.count} 条)`)
|
|
33
|
+
.join('\n')
|
|
34
|
+
: '(暂无现有 Room)';
|
|
35
|
+
return ('你是记忆库的 Room 归类器。给你若干条记忆(带 id)与现有 Room 词表(带计数),' +
|
|
36
|
+
'为每条记忆选出它最可能归属的 1-3 个 Room slug。规则:\n' +
|
|
37
|
+
'1. **优先从现有 Room 词表里选**(保持分类收敛,避免碎片化);\n' +
|
|
38
|
+
'2. 词表确实没有合适主题时,才提议最多 2 个新 slug(小写字母/数字/连字符);\n' +
|
|
39
|
+
'3. 完全无法判断时返回空数组(宁缺毋滥,不硬猜)。\n' +
|
|
40
|
+
`现有 Room 词表:\n${catalog}\n\n` +
|
|
41
|
+
'输出 JSON 数组:[{"id":"<原id>","rooms":["slug1","slug2"]}]。只输出 JSON,不要多余文字。');
|
|
42
|
+
}
|
|
43
|
+
/** 候选标注器:一批孤儿记录 → 候选 Room(优先现有词表;失败返回空,调用方零改动)。 */
|
|
44
|
+
export async function roomCandidateChunk(ctx, cfg, logger, chunk, roomCatalog) {
|
|
45
|
+
const list = chunk.map((r, i) => `${i + 1}. id=${r.id}\n${r.content.slice(0, 300)}`).join('\n\n');
|
|
46
|
+
try {
|
|
47
|
+
const raw = await callLLM(ctx, cfg, {
|
|
48
|
+
system: roomCandidateSystemPrompt(roomCatalog),
|
|
49
|
+
user: `记忆列表:\n${list}`,
|
|
50
|
+
maxTokens: 4096,
|
|
51
|
+
layer: 'l1-extract',
|
|
52
|
+
logger,
|
|
53
|
+
});
|
|
54
|
+
const parsed = parseJsonLogged(raw, 'Room 候选标注', logger);
|
|
55
|
+
if (!Array.isArray(parsed))
|
|
56
|
+
return [];
|
|
57
|
+
const shape = parsed
|
|
58
|
+
.map((item) => ({
|
|
59
|
+
id: typeof item?.id === 'string' ? item.id : '',
|
|
60
|
+
rooms: Array.isArray(item?.rooms) ? item.rooms : [],
|
|
61
|
+
}))
|
|
62
|
+
.filter((x) => x.id !== '');
|
|
63
|
+
return parseRoomCandidateItems(shape, chunk.map((r) => r.id), roomCatalog);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
logger.warn(`[memory] Room 候选标注块失败(跳过 ${chunk.length} 条,原记录未改): ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** 活跃孤儿的选择器:untagged + 未退场,跳过已 skipped 的。 */
|
|
71
|
+
function selectPendingOrphans(l1, limit) {
|
|
72
|
+
const out = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
for (let offset = 0; offset < 2000; offset += 200) {
|
|
75
|
+
const page = l1.list({ untagged: true, retired: false, limit: 200, offset });
|
|
76
|
+
for (const r of page.items) {
|
|
77
|
+
if (seen.has(r.id))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(r.id);
|
|
80
|
+
if (r.metadata?.[ROOM_REVIEW_KEY] === 'skipped')
|
|
81
|
+
continue;
|
|
82
|
+
out.push(r);
|
|
83
|
+
if (out.length >= limit)
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
if (page.items.length < 200)
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
export async function annotateOrphanCandidates(ctx, cfg, io, limit = 40, overrides = {}) {
|
|
92
|
+
const selected = selectPendingOrphans(io.l1, limit);
|
|
93
|
+
if (selected.length === 0)
|
|
94
|
+
return { selected: 0, candidates: 0, written: 0 };
|
|
95
|
+
// 合并词表:注册表条目排前(附 description,对 LLM 归类价值最高),封顶 120;
|
|
96
|
+
// 无注册表 = 纯自生长聚合(计数降序)。整批共享一张词表(一次 LLM 调用)。
|
|
97
|
+
const grown = io.l1.listRooms();
|
|
98
|
+
const regActive = io.registry?.listActive() ?? [];
|
|
99
|
+
const regSet = new Set(regActive.map((e) => e.slug));
|
|
100
|
+
const merged = [
|
|
101
|
+
...regActive.map((e) => ({ room: e.slug, count: 0, label: e.label, description: e.description })),
|
|
102
|
+
...grown.filter((r) => !regSet.has(r.room)).map((r) => ({ room: r.room, count: r.count })),
|
|
103
|
+
].slice(0, 120);
|
|
104
|
+
const rows = overrides.chunkAnnotator !== undefined
|
|
105
|
+
? await overrides.chunkAnnotator(selected, merged)
|
|
106
|
+
: await roomCandidateChunk(ctx, cfg, io.logger, selected, merged);
|
|
107
|
+
let written = 0;
|
|
108
|
+
for (const { id, rooms: candidates } of rows) {
|
|
109
|
+
if (candidates.length === 0)
|
|
110
|
+
continue;
|
|
111
|
+
const fresh = io.l1.getByIds([id])[0];
|
|
112
|
+
if (!fresh)
|
|
113
|
+
continue;
|
|
114
|
+
const meta = { ...(fresh.metadata ?? {}) };
|
|
115
|
+
meta[ROOM_CANDIDATES_KEY] = candidates;
|
|
116
|
+
meta[ROOM_REVIEW_KEY] = 'pending';
|
|
117
|
+
if (io.l1.patchMetadata(id, meta))
|
|
118
|
+
written++;
|
|
119
|
+
}
|
|
120
|
+
io.logger.info(`[memory] 孤儿候选预标记:选中 ${selected.length},产候选 ${rows.length},写 pending ${written} 条(待 agent 复查)`);
|
|
121
|
+
return { selected: selected.length, candidates: rows.length, written };
|
|
122
|
+
}
|
|
123
|
+
/** 待复查队列:pending 且有候选的活跃孤儿,按更新时间升序(最旧的先复查)。 */
|
|
124
|
+
function pendingQueue(l1) {
|
|
125
|
+
const out = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (let offset = 0; offset < 2000; offset += 200) {
|
|
128
|
+
const page = l1.list({ untagged: true, retired: false, limit: 200, offset });
|
|
129
|
+
for (const r of page.items) {
|
|
130
|
+
if (seen.has(r.id))
|
|
131
|
+
continue;
|
|
132
|
+
seen.add(r.id);
|
|
133
|
+
const meta = (r.metadata ?? {});
|
|
134
|
+
if (meta[ROOM_REVIEW_KEY] === 'pending' && Array.isArray(meta[ROOM_CANDIDATES_KEY]))
|
|
135
|
+
out.push(r);
|
|
136
|
+
}
|
|
137
|
+
if (page.items.length < 200)
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
return out.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
141
|
+
}
|
|
142
|
+
/** merge 单飞哨兵(模块级;与 ruminate/wing-backfill 的时间错开由调用方保证)。 */
|
|
143
|
+
let roomMergeRunning = false;
|
|
144
|
+
/**
|
|
145
|
+
* Room merge/rename 编排(破坏性动作,分类管理 beta.5):
|
|
146
|
+
* - dryRun(缺省 true):只统计 affected + 前 6 条预览,**零写入**;
|
|
147
|
+
* - 实跑:执行前备份(matching rows → rooms-merge-backups/)→ 游标重写
|
|
148
|
+
* (写前重读-合并-写回,其余 metadata 键保全)→ 注册表 markMerged/renameSlug
|
|
149
|
+
* → 返回受影响 (family, scene) 供调用方入队 recluster('room-merge');
|
|
150
|
+
* - 单飞:并发 merge 直接抛错;与 relabel/ruminate 的时间错开由调用方保证。
|
|
151
|
+
* 抛错语义:非法参数/注册表冲突直接抛(调用方转 notice)。
|
|
152
|
+
*/
|
|
153
|
+
export async function mergeRoom(io, from, to, opts = {}) {
|
|
154
|
+
const dryRun = opts.dryRun !== false;
|
|
155
|
+
const cap = Math.min(Math.max(opts.cap ?? 500, 1), 2000);
|
|
156
|
+
if (from === to)
|
|
157
|
+
throw new Error('from 与 to 相同');
|
|
158
|
+
if (!from.trim())
|
|
159
|
+
throw new Error('from 不能为空');
|
|
160
|
+
if (roomMergeRunning)
|
|
161
|
+
throw new Error('Room merge 已在进行中(单飞)');
|
|
162
|
+
const previewRows = io.l1.listByTagAll(from, 7);
|
|
163
|
+
const affected = io.l1.listByTagAll(from, cap + 1).length;
|
|
164
|
+
const preview = previewRows.slice(0, 6).map((r) => r.id);
|
|
165
|
+
if (dryRun)
|
|
166
|
+
return { dryRun: true, affected, preview, applied: 0, hasMore: affected > cap };
|
|
167
|
+
if (roomMergeRunning)
|
|
168
|
+
throw new Error('Room merge 已在进行中(单飞)');
|
|
169
|
+
roomMergeRunning = true;
|
|
170
|
+
try {
|
|
171
|
+
const backup = io.l1.backupTagRecords(from);
|
|
172
|
+
io.logger.info(`[memory] Room merge 备份:${from} → ${to},${backup.count} 条 → ${backup.file}`);
|
|
173
|
+
let applied = 0;
|
|
174
|
+
const hasMore = false; // 循环到收敛,单次调用内 hasMore 恒 false(重写返回值沿用结果形状)
|
|
175
|
+
const families = new Set();
|
|
176
|
+
const scenes = new Set();
|
|
177
|
+
// 游标重写:上限 cap/次,hasMore 时继续(总量受 cap×轮次约束,单飞内收敛)
|
|
178
|
+
for (;;) {
|
|
179
|
+
const r = io.l1.rewriteTag(from, to, cap);
|
|
180
|
+
applied += r.rewritten;
|
|
181
|
+
for (const f of r.families)
|
|
182
|
+
families.add(f);
|
|
183
|
+
for (const sc of r.scenes)
|
|
184
|
+
scenes.add(sc);
|
|
185
|
+
if (!r.hasMore || r.rewritten === 0)
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
await io.registry.markMerged(from, to);
|
|
189
|
+
io.l1.invalidateRooms();
|
|
190
|
+
io.logger.info(`[memory] Room merge 完成:${from} → ${to},重写 ${applied} 条;受影响场景 ${scenes.size} 个`);
|
|
191
|
+
return {
|
|
192
|
+
dryRun: false,
|
|
193
|
+
affected,
|
|
194
|
+
preview,
|
|
195
|
+
applied,
|
|
196
|
+
hasMore,
|
|
197
|
+
backupFile: backup.file,
|
|
198
|
+
...(families.size > 0 || scenes.size > 0 ? { families: [...families], scenes: [...scenes] } : {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
roomMergeRunning = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。
|
|
206
|
+
* 归类语义:改名**不是退役**——merge 编排的 markMerged 会把旧条目置 retired,
|
|
207
|
+
* 这里在改名后恢复其先前状态(active 条目改名后仍 active,不掉出标注词表)。 */
|
|
208
|
+
export async function renameRoom(io, from, to, opts = {}) {
|
|
209
|
+
const priorStatus = io.registry.bySlug?.(from)?.status ?? 'active';
|
|
210
|
+
const r = await mergeRoom(io, from, to, opts);
|
|
211
|
+
if (!r.dryRun && r.applied > 0) {
|
|
212
|
+
await io.registry.renameSlug(from, to);
|
|
213
|
+
if (priorStatus === 'active')
|
|
214
|
+
await io.registry.setStatus?.(to, 'active');
|
|
215
|
+
}
|
|
216
|
+
return r;
|
|
217
|
+
}
|
|
218
|
+
/** 下一条待复查(供 agent 逐个过):记录摘要 + 候选 + 现有 Room 词表 + 剩余数。 */
|
|
219
|
+
export function nextReview(l1) {
|
|
220
|
+
const queue = pendingQueue(l1);
|
|
221
|
+
if (queue.length === 0)
|
|
222
|
+
return null;
|
|
223
|
+
const r = queue[0];
|
|
224
|
+
const meta = (r.metadata ?? {});
|
|
225
|
+
return {
|
|
226
|
+
id: r.id,
|
|
227
|
+
content: r.content.slice(0, 600),
|
|
228
|
+
type: r.type,
|
|
229
|
+
sceneName: r.scene_name,
|
|
230
|
+
candidates: meta[ROOM_CANDIDATES_KEY] ?? [],
|
|
231
|
+
roomCatalog: l1.listRooms().slice(0, 40),
|
|
232
|
+
remaining: queue.length,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** 待复查剩余数(面板/工具兜底信息)。 */
|
|
236
|
+
export function pendingReviewCount(l1) {
|
|
237
|
+
return pendingQueue(l1).length;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 确认复查结果:候选(或 agent 改写的)写入 metadata.tags(**进 Room**),
|
|
241
|
+
* 清候选、标 confirmed。**高权限门控由调用方执行**(与 memory_delete 同款)。
|
|
242
|
+
* 返回写入的 tags;id 不存在/状态非法返回 null。
|
|
243
|
+
*/
|
|
244
|
+
export function confirmReview(l1, id, rooms) {
|
|
245
|
+
const found = l1.getByIds([id])[0];
|
|
246
|
+
if (!found)
|
|
247
|
+
return null;
|
|
248
|
+
const meta = { ...(found.metadata ?? {}) };
|
|
249
|
+
// 已确认/已跳过的不允许重复确认(防止 agent 循环空转)
|
|
250
|
+
if (meta[ROOM_REVIEW_KEY] === 'confirmed')
|
|
251
|
+
return null;
|
|
252
|
+
const cleaned = [];
|
|
253
|
+
for (const r of rooms) {
|
|
254
|
+
if (typeof r !== 'string')
|
|
255
|
+
continue;
|
|
256
|
+
const slug = r.trim().toLowerCase();
|
|
257
|
+
if (!slug || !isTag(slug) || cleaned.includes(slug))
|
|
258
|
+
continue;
|
|
259
|
+
cleaned.push(slug);
|
|
260
|
+
}
|
|
261
|
+
if (cleaned.length === 0)
|
|
262
|
+
return null;
|
|
263
|
+
meta.tags = cleaned;
|
|
264
|
+
delete meta[ROOM_CANDIDATES_KEY];
|
|
265
|
+
meta[ROOM_REVIEW_KEY] = 'confirmed';
|
|
266
|
+
if (!l1.patchMetadata(id, meta))
|
|
267
|
+
return null;
|
|
268
|
+
l1.invalidateRooms();
|
|
269
|
+
return { tags: cleaned };
|
|
270
|
+
}
|
|
271
|
+
/** 跳过:标 skipped(不再进 pending 队列)。已 confirmed 的记录不受 skip 影响(挂了 Room 的不算长尾)。 */
|
|
272
|
+
export function skipReview(l1, id) {
|
|
273
|
+
const found = l1.getByIds([id])[0];
|
|
274
|
+
if (!found)
|
|
275
|
+
return false;
|
|
276
|
+
const meta = { ...(found.metadata ?? {}) };
|
|
277
|
+
if (meta[ROOM_REVIEW_KEY] === 'confirmed')
|
|
278
|
+
return false;
|
|
279
|
+
meta[ROOM_REVIEW_KEY] = 'skipped';
|
|
280
|
+
return l1.patchMetadata(id, meta);
|
|
281
|
+
}
|
package/dist/stats.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { type MemoryConfig } from './config.js';
|
|
|
3
3
|
import { type RecallSessionStats } from './hooks/recall.js';
|
|
4
4
|
import type { RebuildController } from './pipeline/rebuild.js';
|
|
5
5
|
import type { RuminateController } from './pipeline/ruminate.js';
|
|
6
|
+
import type { RoomRegistryStore } from './store/rooms-registry.js';
|
|
7
|
+
import type { RecallUsageStore } from './store/recall-usage.js';
|
|
6
8
|
import { type LiveSettingsHandle } from './settings.js';
|
|
7
9
|
import type { GraphStore } from './store/graph-store.js';
|
|
8
10
|
import type { L0Store } from './store/l0.js';
|
|
@@ -23,7 +25,7 @@ export interface MemoryStatusSource {
|
|
|
23
25
|
pending(): number;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
|
-
* 端点全集运行时清单(
|
|
28
|
+
* 端点全集运行时清单(与 tests/contract-keys.test.ts 的 ENDPOINTS 及
|
|
27
29
|
* contract.ts 类型映射表三方对齐,漂移由键集 diff 测试暴露)。
|
|
28
30
|
* 注意:本清单同时是 HTTP 前缀路由 `/dsh-memory/rpc/<短名>` 的**放行白名单**
|
|
29
31
|
* (见下方 SHORT_ENDPOINTS),漏一条 = 该端点在面板里静默消失(404 被客户端
|
|
@@ -77,6 +79,9 @@ interface MemoryRpcSources {
|
|
|
77
79
|
rebuild?: RebuildController;
|
|
78
80
|
embedManager?: EmbeddingManager;
|
|
79
81
|
sessionInfo?: SessionInfoSource;
|
|
82
|
+
/** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
|
|
83
|
+
/** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
|
|
84
|
+
roomRegistry?: RoomRegistryStore;
|
|
80
85
|
}
|
|
81
86
|
/** 端点 deps 的注入面(ctx/cfg/stores/logger 由调用方绑定,其余由此处决定)。 */
|
|
82
87
|
export type EndpointDepsInput = Omit<EndpointDeps, 'ctx' | 'cfg' | 'stores' | 'logger'>;
|
|
@@ -92,19 +97,25 @@ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, store
|
|
|
92
97
|
scenes: Record<MemoryFamily, SceneStore>;
|
|
93
98
|
persona: Record<MemoryFamily, PersonaStore>;
|
|
94
99
|
state: StateStore;
|
|
100
|
+
/** 召回使用统计(可选)。 */
|
|
101
|
+
usage?: RecallUsageStore;
|
|
95
102
|
/** 记忆后端(后台边界);未装配时回退为包 l1 的进程内实现。 */
|
|
96
103
|
backend?: MemoryBackend;
|
|
97
104
|
/** 图谱存储(可选:未装配时图谱端点返空,不报错)。 */
|
|
98
105
|
graph?: GraphStore;
|
|
99
106
|
}, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager, sessionInfo?: SessionInfoSource,
|
|
100
107
|
/** 反刍控制器(存储降级时为 undefined):经 buildEndpointDeps 落入 deps.ruminate。 */
|
|
101
|
-
ruminate?: RuminateController
|
|
108
|
+
ruminate?: RuminateController,
|
|
109
|
+
/** Room 分类管理依赖(注册表;缺省 = rooms-get 不带 registry、rooms-export 恒 404 语义)。 */
|
|
110
|
+
roomRegistry?: RoomRegistryStore): void;
|
|
102
111
|
export interface EndpointDeps {
|
|
103
112
|
ctx: Context;
|
|
104
113
|
cfg: MemoryConfig;
|
|
105
114
|
stores: {
|
|
106
115
|
l0: L0Store;
|
|
107
116
|
l1: L1Store;
|
|
117
|
+
/** 召回使用统计(可选:未装配 = 列表不带 usage/usageSummary)。 */
|
|
118
|
+
usage?: RecallUsageStore;
|
|
108
119
|
scenes: Record<MemoryFamily, SceneStore>;
|
|
109
120
|
persona: Record<MemoryFamily, PersonaStore>;
|
|
110
121
|
state: StateStore;
|
|
@@ -121,10 +132,15 @@ export interface EndpointDeps {
|
|
|
121
132
|
ruminate?: RuminateController;
|
|
122
133
|
embedManager?: EmbeddingManager;
|
|
123
134
|
sessionInfo?: SessionInfoSource;
|
|
135
|
+
/** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
|
|
136
|
+
/** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
|
|
137
|
+
roomRegistry?: RoomRegistryStore;
|
|
124
138
|
}
|
|
125
139
|
/** 端点分发表(导出供测试直调:可精确注入 rebuild/ruminate 等可选控制器,验证 deps 接线)。 */
|
|
126
140
|
export declare function handleEndpoint(endpoint: string, payload: unknown, deps: EndpointDeps): Promise<unknown>;
|
|
127
141
|
/** 浏览器卡片字段(比 MemoryRecord 精简,去掉大 metadata;Hall 从 metadata 提取)。 */
|
|
142
|
+
/** 老化权重(与读路径 applyDecayWeight 同式):max(0.5, 0.5^(Δ天/半衰期));半衰期 ≤0 = 关 → null。 */
|
|
143
|
+
export declare function agingWeightOf(updatedAtMs: number | undefined, halfLifeDays: number, now?: number): number | null;
|
|
128
144
|
export declare function hitToUiRecord(r: {
|
|
129
145
|
id: string;
|
|
130
146
|
content: string;
|
|
@@ -141,4 +157,12 @@ export declare function hitToUiRecord(r: {
|
|
|
141
157
|
family?: string;
|
|
142
158
|
/** 退场判据:`valid_to` 闭合即已退场(软删)。由 `listL1`/`getByIds` 透传。 */
|
|
143
159
|
validTo?: number;
|
|
160
|
+
}, extra?: {
|
|
161
|
+
agingWeight?: number | null;
|
|
162
|
+
usage?: {
|
|
163
|
+
attempts: number;
|
|
164
|
+
used: number;
|
|
165
|
+
lastAttemptAt: number | null;
|
|
166
|
+
lastUsedAt: number | null;
|
|
167
|
+
} | null;
|
|
144
168
|
}): UiRecord;
|