dsh-prime-memory 0.20.0-beta.9 → 0.20.2

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/dist/index.js CHANGED
@@ -36,10 +36,8 @@ import { SlotStore } from './store/slots.js';
36
36
  import { registerMemoryTools } from './tools/index.js';
37
37
  import { registerSlotTools } from './tools/slots.js';
38
38
  import { registerSlotRecall } from './hooks/slot-recall.js';
39
- import { registerLongTaskMonitor } from './hooks/long-task.js';
40
- import { ContextUsageStore } from './store/context-usage.js';
41
- import { TodoRefStore } from './store/todo-ref.js';
42
39
  import { RoomRegistryStore } from './store/rooms-registry.js';
40
+ import { RecallUsageStore } from './store/recall-usage.js';
43
41
  import { registerSlotsProjection } from './projection/slots.js';
44
42
  import { errDetail, withFileLog } from './util/filelog.js';
45
43
  import { buildRouteChain, resolveModelRoute, invalidateEffortCache } from './llm.js';
@@ -162,6 +160,7 @@ export async function apply(ctx, config) {
162
160
  }
163
161
  }
164
162
  const stores = {
163
+ usage: undefined, // 装配于下方(依赖 dataDir 就绪)
165
164
  l0: new L0Store(dataDir, db, embed, logger),
166
165
  l1: new L1Store(dataDir, db, embed, config.recall.strategy, logger, config.recall.decayHalfLifeDays,
167
166
  // §D 第 3 路:图谱回链。图谱不可用时 searchNodes 自带 no-op,不影响双路。
@@ -364,28 +363,17 @@ export async function apply(ctx, config) {
364
363
  if (storageOk) {
365
364
  flushL0 = registerCapture(ctx, config, runner, stores.l0, logger, live, modes);
366
365
  }
367
- // 长任务模式(cfg.longTask.enabled 门控,默认关):官方 usage 上下文计量 +
368
- // todo/write 快照参考(仅长任务会话;写 pinned 槽位走 slot-recall 常驻注入)。
369
- const contextUsage = new ContextUsageStore(logger);
370
- const todoRef = new TodoRefStore();
371
- // Room 注册表(分类管理 beta.4;损坏降级为纯自生长目录)
372
- if (storageOk) {
373
- registerLongTaskMonitor(ctx, {
374
- cfg: config,
375
- modes,
376
- slots: stores.slots,
377
- contextUsage,
378
- todoRef,
379
- logger,
380
- });
381
- }
382
- const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir);
366
+ // 召回使用统计(进 topN 才算 used;热路径只碰内存,去抖落盘)
367
+ const recallUsage = new RecallUsageStore(dataDir, logger);
368
+ await recallUsage.init();
369
+ stores.usage = recallUsage;
370
+ const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir, recallUsage);
383
371
  runner.setAfterRun(recall.invalidateProfile);
384
372
  registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
385
373
  // 激活槽位(active slot):工具面 + 常驻注入 + 服务端投影(均走 ctx.effect,可撤销)
386
374
  registerSlotTools(ctx, config, stores.slots, logger, modes, live, stores.l1);
387
375
  const slotRecall = registerSlotRecall(ctx, config, stores.slots, logger, live);
388
- registerSlotsProjection(ctx, stores.slots);
376
+ registerSlotsProjection(ctx, stores.slots, stores.l1);
389
377
  void slotRecall;
390
378
  registerMemoryRpc(ctx, config, stores, logger, {
391
379
  degraded: () => !storageOk || db.isDegraded(),
@@ -400,7 +388,7 @@ export async function apply(ctx, config) {
400
388
  runnerView: (sid, mode) => runner.sessionView(sid, mode),
401
389
  l0Count: (sid) => stores.l0.countBySession(sid),
402
390
  capabilities: () => db.getCapabilities(),
403
- }, ruminate, storageOk ? { runner, slots: stores.slots, contextUsage, todoRef } : undefined, storageOk ? roomRegistry : undefined);
391
+ }, ruminate, storageOk ? roomRegistry : undefined);
404
392
  // bench 控制服务(config.benchControl 门控,默认关):仅基准/调试部署注册,
405
393
  // 供同进程的 bench-runner lifecycle 赛道触发 rebuild / 设置会话档位
406
394
  // (宿主侧 RPC 无 call(),见 bench-control.ts)
@@ -10,6 +10,11 @@
10
10
  * 已是 string 的变量收窄成 `never`(该分支内无法再 `.slice`,已在 wing-backfill 踩过)。
11
11
  */
12
12
  export declare function isWingId(v: unknown): boolean;
13
+ /**
14
+ * 两级 Room slug(`major` 或 `major/minor`,各段满足 isTag;至多一个 slash)。
15
+ * Room 注册表/归类写回的 tags 域;与 rooms-registry.ts 同一事实源(就近定义避免依赖环)。
16
+ */
17
+ export declare function isRoomSlug(v: unknown): boolean;
13
18
  /** 同为布尔谓词(理由见 isWingId 注释)。 */
14
19
  export declare function isTag(v: unknown): boolean;
15
20
  /**
@@ -31,6 +31,16 @@ export function isWingId(v) {
31
31
  }
32
32
  /** 标签(Room 的构成单元)校验:小写字母数字连字符,1-32 字符。 */
33
33
  const TAG_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
34
+ /**
35
+ * 两级 Room slug(`major` 或 `major/minor`,各段满足 isTag;至多一个 slash)。
36
+ * Room 注册表/归类写回的 tags 域;与 rooms-registry.ts 同一事实源(就近定义避免依赖环)。
37
+ */
38
+ export function isRoomSlug(v) {
39
+ if (typeof v !== 'string')
40
+ return false;
41
+ const parts = v.split('/');
42
+ return parts.length <= 2 && parts.every((p) => TAG_RE.test(p));
43
+ }
34
44
  /** 同为布尔谓词(理由见 isWingId 注释)。 */
35
45
  export function isTag(v) {
36
46
  return typeof v === 'string' && TAG_RE.test(v);
@@ -47,7 +57,7 @@ export function normTags(v, max = 3) {
47
57
  if (typeof raw !== 'string')
48
58
  continue;
49
59
  const t = raw.trim().toLowerCase();
50
- if (!isTag(t) || out.includes(t))
60
+ if (!(isTag(t) || isRoomSlug(t)) || out.includes(t))
51
61
  continue;
52
62
  out.push(t);
53
63
  if (out.length >= max)
@@ -15,7 +15,7 @@ export async function processSceneReclusterJobs(source, scenes, logger, maxJobs
15
15
  const family = job.family === 'work' ? 'work' : 'chat';
16
16
  const store = scenes[family];
17
17
  // 快照在 try 外声明:catch 的回滚要用它
18
- let snapshots = [];
18
+ const snapshots = [];
19
19
  try {
20
20
  // ① 内存快照:按场景名(文件 basename)匹配当前存在的摘要文件
21
21
  const all = await store.list();
@@ -17,6 +17,20 @@ export interface SlotsViewSlot {
17
17
  kind: string;
18
18
  status: string;
19
19
  priority: number;
20
+ /** 原始引用(L1 record_id / 文件路径 / URL);存在才校验,旧 checkpoint 无此字段照常通过。 */
21
+ refs?: string[];
22
+ /**
23
+ * L1 record_id 类引用的解析视图(v0.20.2):ref → 名称简述(content 首行截断)。
24
+ * 前端展示用;解析发生在投影帧构建时(帧随 slots rev 刷新,L1 记录后续编辑
25
+ * 不回灌旧帧——引用集在写入时即冻结,可接受)。缺失/已退场记录不产生条目,
26
+ * 前端回落显示原始 ref。
27
+ */
28
+ refViews?: SlotRefView[];
29
+ }
30
+ /** 一条 record_id 引用的展示解析。 */
31
+ export interface SlotRefView {
32
+ ref: string;
33
+ title: string;
20
34
  }
21
35
  export interface SlotsProjectionState {
22
36
  rev: number;
@@ -51,5 +65,13 @@ interface SchemaLike<T> {
51
65
  export declare const stateSchema: SchemaLike<SlotsProjectionState>;
52
66
  /** wire view 校验:registry 在 view 出网前调用(也覆盖 live drive 的发布路径)。 */
53
67
  export declare const viewSchema: SchemaLike<SlotsView>;
54
- export declare function registerSlotsProjection(ctx: Context, store: SlotStore): void;
68
+ /** 投影需要的 L1 只读面(结构化声明,避免跨模块类型耦合)。 */
69
+ export interface L1LookupFace {
70
+ getByIds(ids: string[]): Array<{
71
+ id: string;
72
+ content: string;
73
+ type: string;
74
+ }>;
75
+ }
76
+ export declare function registerSlotsProjection(ctx: Context, store: SlotStore, l1?: L1LookupFace): void;
55
77
  export {};
@@ -40,6 +40,26 @@ function parseSlotsPayload(value, what) {
40
40
  if (typeof priority !== 'number' || !Number.isInteger(priority) || priority < 0 || priority > 100) {
41
41
  invalid(at, 'priority 必须是 0-100 的整数');
42
42
  }
43
+ // refs / refViews:存在才校验(附加字段,旧 checkpoint 行没有也必须整条通过)
44
+ if (item.refs !== undefined) {
45
+ if (!Array.isArray(item.refs))
46
+ invalid(at, 'refs 必须是字符串数组');
47
+ item.refs.forEach((r, i) => {
48
+ if (typeof r !== 'string')
49
+ invalid(at + '.refs[' + i + ']', '必须是字符串');
50
+ });
51
+ }
52
+ if (item.refViews !== undefined) {
53
+ if (!Array.isArray(item.refViews))
54
+ invalid(at, 'refViews 必须是数组');
55
+ item.refViews.forEach((v, i) => {
56
+ const at2 = at + '.refViews[' + i + ']';
57
+ const view = requireRecord(v, at2);
58
+ if (typeof view.ref !== 'string' || typeof view.title !== 'string') {
59
+ invalid(at2, 'ref/title 必须是字符串');
60
+ }
61
+ });
62
+ }
43
63
  });
44
64
  return value;
45
65
  }
@@ -51,9 +71,38 @@ export const stateSchema = {
51
71
  export const viewSchema = {
52
72
  parse: (value) => parseSlotsPayload(value, 'memorySlots.view'),
53
73
  };
54
- function buildState(store) {
74
+ /** L1 record_id 的引用形态(newId('mem') 生成)。 */
75
+ const RECORD_REF_PREFIX = 'mem_';
76
+ /** refView 名称简述的截断上限(展示用,全文走 memory_receipts / memory_read_scene)。 */
77
+ const REF_TITLE_MAX = 80;
78
+ /**
79
+ * 把槽位 refs 里的 L1 record_id 解析成名称简述([type] content 首行截断)。
80
+ * 只解析 record_id 类引用;路径/URL 保持字面。缺失/已退场记录不产生条目。
81
+ */
82
+ function resolveRefViews(refs, l1) {
83
+ const recordRefs = refs.filter((r) => r.startsWith(RECORD_REF_PREFIX));
84
+ if (recordRefs.length === 0 || l1 === undefined)
85
+ return undefined;
86
+ const byId = new Map(l1.getByIds(recordRefs).map((r) => [r.id, r]));
87
+ const views = [];
88
+ for (const ref of recordRefs) {
89
+ const record = byId.get(ref);
90
+ if (record === undefined)
91
+ continue;
92
+ const firstLine = record.content.split('\n', 1)[0] ?? '';
93
+ const summary = firstLine.length > REF_TITLE_MAX ? firstLine.slice(0, REF_TITLE_MAX) + '…' : firstLine;
94
+ views.push({ ref, title: '[' + record.type + '] ' + summary });
95
+ }
96
+ return views.length > 0 ? views : undefined;
97
+ }
98
+ function buildState(store, l1) {
55
99
  const snap = store.projectionSnapshot();
56
- return { rev: store.revision(), count: snap.count, openCount: snap.openCount, slots: snap.slots };
100
+ const slots = snap.slots.map((slot) => {
101
+ if (slot.refs === undefined)
102
+ return slot;
103
+ return { ...slot, refViews: resolveRefViews(slot.refs, l1) };
104
+ });
105
+ return { rev: store.revision(), count: snap.count, openCount: snap.openCount, slots };
57
106
  }
58
107
  /**
59
108
  * view 引用稳定:同一 state 连续两次 view() 满足 Object.is(注册表据此判脏,
@@ -75,27 +124,22 @@ function view(state) {
75
124
  }
76
125
  /**
77
126
  * apply(state, event):同步。
78
- * - 仅 tool/result(settled,非 error)才检查:tool/call 在 store 变更提交前发生,按
79
- * call 折会读到 stale;tool/result settled 才保证变更已落库(对齐 deliverables-fold)。
80
- * - 闭包 SlotStore,同步读 revision():rev 未变 → 本插件槽位未动 → 返回同引用
81
- * (满足 I1b,避免 republish thrash);rev 变 → 重建快照。
82
- * - 任何无关事件 → 返回同引用(不 spread)。
127
+ * - **任意已提交事件都判脏**(v0.18.4 起):此前仅在 tool/result(settled、非 error)
128
+ * 时检查,实际运行中暴露出脆弱性——写入后的投影帧依赖"该会话恰好再来一条
129
+ * tool/result"才刷新;跨会话场景(会话 A 写、会话 B 看)更是要等 B 自己的下一次
130
+ * 工具调用。改为对所有事件做 O(1) revision 比较:rev 未变 → 同引用(满足 I1b,
131
+ * 无 republish thrash);rev 变 → 立即重建。写入后的下一条任意事件(用户消息、
132
+ * turn 边界、其他工具结果)都会把新帧推给客户端。
133
+ * - isError 守卫一并移除:rev 只在 store 真实变更后递增(persist 内联 RMW),
134
+ * 事件成败与否不影响"store 是否变了"这一事实;错误事件后重建读到的仍是
135
+ * 当前真实快照,不会引入错误数据。
83
136
  */
84
- function applySlotsEvent(state, event, store) {
85
- if (event.type !== 'tool/result')
86
- return state;
87
- const data = event.data;
88
- // v4 native(宿主 ≥0.1.7-rc.1):isError 在 message 顶层;v3 wrapper:块上 isError(历史兼容)。
89
- const isError = data?.message?.isError === true ||
90
- (Array.isArray(data?.message?.content) &&
91
- data.message.content.some((c) => c.isError === true));
92
- if (isError)
93
- return state;
137
+ function applySlotsEvent(state, event, store, l1) {
94
138
  if (store.revision() === state.rev)
95
139
  return state;
96
- return buildState(store);
140
+ return buildState(store, l1);
97
141
  }
98
- export function registerSlotsProjection(ctx, store) {
142
+ export function registerSlotsProjection(ctx, store, l1) {
99
143
  ctx.inject(['sessionProjections'], (injected) => {
100
144
  const registry = injected
101
145
  .sessionProjections;
@@ -104,8 +148,8 @@ export function registerSlotsProjection(ctx, store) {
104
148
  registry.register({
105
149
  key: MEMORY_SLOTS_KEY,
106
150
  stateSchema,
107
- init: () => buildState(store),
108
- apply: (state, event) => applySlotsEvent(state, event, store),
151
+ init: () => buildState(store, l1),
152
+ apply: (state, event) => applySlotsEvent(state, event, store, l1),
109
153
  wire: { viewSchema, view },
110
154
  stateVersion: 0,
111
155
  });
@@ -65,6 +65,11 @@ export interface RoomMergeStore extends RoomReviewStore {
65
65
  export interface RoomRegistryMerge {
66
66
  markMerged(fromSlug: string, toSlug: string): Promise<boolean>;
67
67
  renameSlug(oldSlug: string, newSlug: string): Promise<boolean>;
68
+ /** rename 收尾恢复先前状态用(active 条目改名后不掉出标注词表)。 */
69
+ bySlug?(slug: string): {
70
+ status: 'active' | 'retired';
71
+ } | undefined;
72
+ setStatus?(slug: string, status: 'active' | 'retired'): Promise<boolean>;
68
73
  }
69
74
  export interface RoomCandidate {
70
75
  id: string;
@@ -153,7 +158,9 @@ export declare function mergeRoom(io: {
153
158
  dryRun?: boolean;
154
159
  cap?: number;
155
160
  }): Promise<MergeRoomResult>;
156
- /** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。 */
161
+ /** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。
162
+ * 归类语义:改名**不是退役**——merge 编排的 markMerged 会把旧条目置 retired,
163
+ * 这里在改名后恢复其先前状态(active 条目改名后仍 active,不掉出标注词表)。 */
157
164
  export declare function renameRoom(io: {
158
165
  l1: RoomMergeStore;
159
166
  registry: RoomRegistryMerge;
@@ -171,10 +171,10 @@ export async function mergeRoom(io, from, to, opts = {}) {
171
171
  const backup = io.l1.backupTagRecords(from);
172
172
  io.logger.info(`[memory] Room merge 备份:${from} → ${to},${backup.count} 条 → ${backup.file}`);
173
173
  let applied = 0;
174
- let hasMore = false;
174
+ const hasMore = false; // 循环到收敛,单次调用内 hasMore 恒 false(重写返回值沿用结果形状)
175
175
  const families = new Set();
176
176
  const scenes = new Set();
177
- // 游标重写:上限 cap/次,hasMore 时继续(总量受 cap×轮次约束,单飞内收敛)
177
+ // 游标重写:上限 cap/次,r.hasMore 时继续(总量受 cap×轮次约束,单飞内收敛)
178
178
  for (;;) {
179
179
  const r = io.l1.rewriteTag(from, to, cap);
180
180
  applied += r.rewritten;
@@ -202,11 +202,17 @@ export async function mergeRoom(io, from, to, opts = {}) {
202
202
  roomMergeRunning = false;
203
203
  }
204
204
  }
205
- /** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。 */
205
+ /** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。
206
+ * 归类语义:改名**不是退役**——merge 编排的 markMerged 会把旧条目置 retired,
207
+ * 这里在改名后恢复其先前状态(active 条目改名后仍 active,不掉出标注词表)。 */
206
208
  export async function renameRoom(io, from, to, opts = {}) {
209
+ const priorStatus = io.registry.bySlug?.(from)?.status ?? 'active';
207
210
  const r = await mergeRoom(io, from, to, opts);
208
- if (!r.dryRun && r.applied > 0)
211
+ if (!r.dryRun && r.applied > 0) {
209
212
  await io.registry.renameSlug(from, to);
213
+ if (priorStatus === 'active')
214
+ await io.registry.setStatus?.(to, 'active');
215
+ }
210
216
  return r;
211
217
  }
212
218
  /** 下一条待复查(供 agent 逐个过):记录摘要 + 候选 + 现有 Room 词表 + 剩余数。 */
package/dist/stats.d.ts CHANGED
@@ -4,15 +4,12 @@ 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
6
  import type { RoomRegistryStore } from './store/rooms-registry.js';
7
+ import type { RecallUsageStore } from './store/recall-usage.js';
7
8
  import { type LiveSettingsHandle } from './settings.js';
8
9
  import type { GraphStore } from './store/graph-store.js';
9
10
  import type { L0Store } from './store/l0.js';
10
11
  import type { L1Store } from './store/l1.js';
11
12
  import type { PersonaStore } from './store/persona.js';
12
- import type { SlotStore } from './store/slots.js';
13
- import type { ContextUsageStore } from './store/context-usage.js';
14
- import type { TodoRefStore } from './store/todo-ref.js';
15
- import type { MemoryRunner } from './pipeline/runner.js';
16
13
  import type { SceneStore } from './store/scenes.js';
17
14
  import type { SessionModeStore } from './store/session-modes.js';
18
15
  import type { EmbeddingManager } from './store/embedding-source.js';
@@ -72,29 +69,6 @@ export interface SessionInfoSource {
72
69
  }
73
70
  import type { MemoryOccupancy, UiRecord } from './contract.js';
74
71
  export type { MemoryStats } from './contract.js';
75
- /** 注册状态 RPC(web 侧 connection 服务可选,缺失时跳过,不影响插件主体)。 */
76
- /** 长任务端点依赖(可选:未装配时端点返 enabled:false,不报错)。 */
77
- export interface LongTaskEndpointDeps {
78
- runner: MemoryRunner;
79
- slots: SlotStore;
80
- contextUsage: ContextUsageStore;
81
- todoRef: TodoRefStore;
82
- }
83
- /**
84
- * 尾部 turn 压缩:把 (水位线, maxCapturedTurn] 内最近的最近 N 轮强制蒸馏入记忆
85
- * (force 跳过阈值;L1 去重与水位线双重防重)。off 档会话保持完全隐身(不压缩)。
86
- * 无锚点老数据只在首次压缩(无水位线)时纳入——宁少收不重蒸。
87
- */
88
- export declare function compressTailTurns(sessionId: string, turns: number, io: {
89
- modes: SessionModeStore;
90
- l0: L0Store;
91
- runner: MemoryRunner;
92
- logger: MemoryLogger;
93
- }): Promise<{
94
- enqueued: number;
95
- fromTurn: number | null;
96
- toTurn: number | null;
97
- }>;
98
72
  /** registerMemoryRpc 形参中需要落入端点 deps 的部分。 */
99
73
  interface MemoryRpcSources {
100
74
  status?: MemoryStatusSource;
@@ -104,8 +78,6 @@ interface MemoryRpcSources {
104
78
  rebuild?: RebuildController;
105
79
  embedManager?: EmbeddingManager;
106
80
  sessionInfo?: SessionInfoSource;
107
- /** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
108
- longTask?: LongTaskEndpointDeps;
109
81
  /** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
110
82
  roomRegistry?: RoomRegistryStore;
111
83
  }
@@ -123,6 +95,8 @@ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, store
123
95
  scenes: Record<MemoryFamily, SceneStore>;
124
96
  persona: Record<MemoryFamily, PersonaStore>;
125
97
  state: StateStore;
98
+ /** 召回使用统计(可选)。 */
99
+ usage?: RecallUsageStore;
126
100
  /** 记忆后端(后台边界);未装配时回退为包 l1 的进程内实现。 */
127
101
  backend?: MemoryBackend;
128
102
  /** 图谱存储(可选:未装配时图谱端点返空,不报错)。 */
@@ -130,8 +104,6 @@ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, store
130
104
  }, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager, sessionInfo?: SessionInfoSource,
131
105
  /** 反刍控制器(存储降级时为 undefined):经 buildEndpointDeps 落入 deps.ruminate。 */
132
106
  ruminate?: RuminateController,
133
- /** 长任务端点依赖(cfg.longTask.enabled 门控;缺省 = 端点恒 disabled)。 */
134
- longTaskDeps?: LongTaskEndpointDeps,
135
107
  /** Room 分类管理依赖(注册表;缺省 = rooms-get 不带 registry、rooms-export 恒 404 语义)。 */
136
108
  roomRegistry?: RoomRegistryStore): void;
137
109
  export interface EndpointDeps {
@@ -140,6 +112,8 @@ export interface EndpointDeps {
140
112
  stores: {
141
113
  l0: L0Store;
142
114
  l1: L1Store;
115
+ /** 召回使用统计(可选:未装配 = 列表不带 usage/usageSummary)。 */
116
+ usage?: RecallUsageStore;
143
117
  scenes: Record<MemoryFamily, SceneStore>;
144
118
  persona: Record<MemoryFamily, PersonaStore>;
145
119
  state: StateStore;
@@ -156,14 +130,14 @@ export interface EndpointDeps {
156
130
  ruminate?: RuminateController;
157
131
  embedManager?: EmbeddingManager;
158
132
  sessionInfo?: SessionInfoSource;
159
- /** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
160
- longTask?: LongTaskEndpointDeps;
161
133
  /** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
162
134
  roomRegistry?: RoomRegistryStore;
163
135
  }
164
136
  /** 端点分发表(导出供测试直调:可精确注入 rebuild/ruminate 等可选控制器,验证 deps 接线)。 */
165
137
  export declare function handleEndpoint(endpoint: string, payload: unknown, deps: EndpointDeps): Promise<unknown>;
166
138
  /** 浏览器卡片字段(比 MemoryRecord 精简,去掉大 metadata;Hall 从 metadata 提取)。 */
139
+ /** 老化权重(与读路径 applyDecayWeight 同式):max(0.5, 0.5^(Δ天/半衰期));半衰期 ≤0 = 关 → null。 */
140
+ export declare function agingWeightOf(updatedAtMs: number | undefined, halfLifeDays: number, now?: number): number | null;
167
141
  export declare function hitToUiRecord(r: {
168
142
  id: string;
169
143
  content: string;
@@ -180,4 +154,12 @@ export declare function hitToUiRecord(r: {
180
154
  family?: string;
181
155
  /** 退场判据:`valid_to` 闭合即已退场(软删)。由 `listL1`/`getByIds` 透传。 */
182
156
  validTo?: number;
157
+ }, extra?: {
158
+ agingWeight?: number | null;
159
+ usage?: {
160
+ attempts: number;
161
+ used: number;
162
+ lastAttemptAt: number | null;
163
+ lastUsedAt: number | null;
164
+ } | null;
183
165
  }): UiRecord;