dsh-prime-memory 0.18.5 → 0.18.7

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.
@@ -507,91 +507,10 @@ export interface WingOverviewResponse {
507
507
  * 降级/无 tags 时 `rooms` 为空数组(面板显示"暂无"),不报错。
508
508
  */
509
509
  export interface RoomsGetResponse {
510
- /** Room 列表(按记录数降序,同数按名升序;注册表条目 count=0 并带 source/label)。
511
- * 条目可选字段 source/label 由端点层合并注册表后附带(客户端可选读,零断裂)。 */
510
+ /** Room 列表(按记录数降序,同数按名升序)。 */
512
511
  rooms: RoomCount[];
513
512
  /** Room 总数(= rooms.length,冗余只为前端少写一次 .length)。 */
514
513
  total: number;
515
- /** 孤儿记忆数:没有任何 Room(tags 为空/缺失)的记录条数(与 rooms 同口径含已退场)。 */
516
- orphanCount: number;
517
- /** 注册表条目(全量含 retired;分类管理面板用;未装配 = undefined)。 */
518
- registry?: Array<{
519
- slug: string;
520
- label?: string;
521
- description?: string;
522
- source: 'pre-registered' | 'grown';
523
- aliases?: string[];
524
- status: 'active' | 'retired';
525
- }>;
526
- }
527
- /** dsh-memory/room-register(预注册 Room;高权限:面板 memoryMutate 开启才生效)。 */
528
- export interface RoomRegisterRequest {
529
- slug: string;
530
- label?: string;
531
- description?: string;
532
- /** 收编自生长 slug 时传 grown(缺省 pre-registered;已存在条目忽略此字段)。 */
533
- source?: 'pre-registered' | 'grown';
534
- }
535
- export interface RoomRegisterResponse {
536
- slug: string;
537
- created: boolean;
538
- /** 幂等补全时的提示(如「已存在,只补全缺失字段」)。 */
539
- notice?: string;
540
- }
541
- /** dsh-memory/rooms-export(分类数据导出,CSV 直出)。 */
542
- export interface RoomsExportRequest {
543
- /** rooms = 词表+计数;orphans = 无 Room 绑定的记录清单;records = 按 Room 的记录清单(tag 必传)。 */
544
- kind: 'rooms' | 'orphans' | 'records';
545
- /** 各 kind 通用上限(1..10000,默认 2000)。 */
546
- limit?: number;
547
- /** kind=records 的目标 Room slug。 */
548
- tag?: string;
549
- }
550
- export interface RoomsExportResponse {
551
- kind: 'rooms' | 'orphans' | 'records';
552
- /** CSV 文本(首行表头;
553
- 行尾;RFC 4180 转义)。 */
554
- csv: string;
555
- /** 数据行数(不含表头)。 */
556
- total: number;
557
- /** truncated = 达到 limit 上限,结果可能不完整。 */
558
- truncated: boolean;
559
- }
560
- /** dsh-memory/room-admin(Room 破坏性面管理;高权限:面板 memoryMutate 开启才生效)。 */
561
- export interface RoomAdminRequest {
562
- /** merge/rename:from→to(mergeRoom/renameRoom 编排:预览→实跑+自动备份+入队场景重算);retire:slug 退役/恢复;update:改写 label/description。 */
563
- action: 'merge' | 'rename' | 'retire' | 'update';
564
- /** update 的显示名(可中文;非空才改写)。 */
565
- label?: string;
566
- /** update 的归类说明(喂标注器;非空才改写)。 */
567
- description?: string;
568
- /** merge/rename 的源 Room slug。 */
569
- from?: string;
570
- /** merge/rename 的目标 Room slug。 */
571
- to?: string;
572
- /** retire 的目标 Room slug。 */
573
- slug?: string;
574
- /** retire:true=恢复 active;缺省/false=退役。 */
575
- active?: boolean;
576
- /** retire:同时把该 room 下全部**活跃**记录退场(软删,可在「已退场」区恢复)。 */
577
- withRecords?: boolean;
578
- /** merge/rename:缺省 true 只返预览;实跑需显式 false。 */
579
- dryRun?: boolean;
580
- }
581
- export interface RoomAdminResponse {
582
- notice: string;
583
- /** merge/rename 本次是否 dryRun(预览)。 */
584
- dryRun?: boolean;
585
- /** merge/rename 影响的记录条数(实跑 = 已改写条数)。 */
586
- affected?: number;
587
- /** 预览样例(受影响记录 id 的前几条)。 */
588
- preview?: string[];
589
- /** true = 超过单次上限,需续跑。 */
590
- hasMore?: boolean;
591
- /** 实跑时改写前备份文件名(rooms-merge-backups/)。 */
592
- backupFile?: string;
593
- /** retire withRecords 实际退场的记录条数(软删,可恢复)。 */
594
- retiredRecords?: number;
595
514
  }
596
515
  /** dsh-memory/wing-backfill(一键回填,后台任务;端点立即返回,进度以 wing-overview 轮询)。 */
597
516
  export interface WingBackfillResponse {
@@ -731,11 +650,6 @@ export interface ListRecordsRequest {
731
650
  * Room 由 tags 派生(自生长),故这里传的是**具体 tag 名**而非枚举 id。
732
651
  */
733
652
  tag?: string;
734
- /**
735
- * 孤儿记忆筛选:true = 只列**没有任何 Room(tags 为空/缺失)**的记录。
736
- * 与 `tag` 互斥;同传时 `tag` 优先(客户端保证不同传)。
737
- */
738
- untagged?: boolean;
739
653
  /**
740
654
  * 退场(软删)筛查:三态。省略 = 全部(活跃+已退场混排,靠 `UiRecord.retired`
741
655
  * 徽标区分);`false` = 仅活跃;`true` = 仅已退场。
@@ -772,15 +686,6 @@ export interface UiRecord {
772
686
  sourceAnchors: string[];
773
687
  /** 检索相关度(列表路径无 score → null)。 */
774
688
  score: number | null;
775
- /** 老化权重:读路径衰减系数 max(0.5, 0.5^(Δ天/半衰期))(Δ=updatedAt;半衰期 0=关 → null)。 */
776
- agingWeight?: number | null;
777
- /** 召回使用统计:**进 topN(被注入)才算 used**;参与尝试未进 = 仅 attempts。缺省 = 未装配。 */
778
- usage?: {
779
- attempts: number;
780
- used: number;
781
- lastAttemptAt: number | null;
782
- lastUsedAt: number | null;
783
- } | null;
784
689
  /**
785
690
  * 是否已退场(软删)。`true` 时该记录在活动列表里仍以「已退场」态呈现(置灰 + 徽标),
786
691
  * 可被 `records-restore` 找回——这正是设计要的「不静默消失、可恢复」,而非真删。
@@ -799,13 +704,6 @@ export interface ListRecordsResponse {
799
704
  truncated: boolean;
800
705
  /** 场景筛选下拉选项(仅 offset===0 时附带)。 */
801
706
  scenes?: string[];
802
- /** 召回使用汇总(老化权重效果口径:进 topN 才算 used)。未装配 = undefined。 */
803
- usageSummary?: {
804
- tracked: number;
805
- attempted: number;
806
- used: number;
807
- rate: number;
808
- } | null;
809
707
  /**
810
708
  * Wing 词表(R8 单一事实源:服务端随 list-records 下发,client 不再手抄;仅 offset===0 时附带)。
811
709
  * 含 8 角 + `general`(跨域兜底,存量大 reserved 值仍可筛)。缺省(旧服务端)时 client
@@ -1303,9 +1201,6 @@ export interface DshMemoryRequestMap {
1303
1201
  'dsh-memory/session-mode-set': SessionModeSetRequest;
1304
1202
  'dsh-memory/wing-overview': Record<string, never>;
1305
1203
  'dsh-memory/rooms-get': Record<string, never>;
1306
- 'dsh-memory/rooms-export': RoomsExportRequest;
1307
- 'dsh-memory/room-register': RoomRegisterRequest;
1308
- 'dsh-memory/room-admin': RoomAdminRequest;
1309
1204
  'dsh-memory/wing-backfill': Record<string, never>;
1310
1205
  'dsh-memory/session-stats': SessionStatsRequest;
1311
1206
  'dsh-memory/settings-get': Record<string, never>;
@@ -1351,9 +1246,6 @@ export interface DshMemoryResponseMap {
1351
1246
  'dsh-memory/session-mode-set': SessionModeSetResponse;
1352
1247
  'dsh-memory/wing-overview': WingOverviewResponse;
1353
1248
  'dsh-memory/rooms-get': RoomsGetResponse;
1354
- 'dsh-memory/rooms-export': RoomsExportResponse;
1355
- 'dsh-memory/room-register': RoomRegisterResponse;
1356
- 'dsh-memory/room-admin': RoomAdminResponse;
1357
1249
  'dsh-memory/wing-backfill': WingBackfillResponse;
1358
1250
  'dsh-memory/session-stats': SessionStatsResponse;
1359
1251
  'dsh-memory/settings-get': SettingsGetResponse;
@@ -22,7 +22,6 @@ import type { L1Store } from '../store/l1.js';
22
22
  import type { PersonaStore } from '../store/persona.js';
23
23
  import type { SceneStore } from '../store/scenes.js';
24
24
  import type { SessionModeStore } from '../store/session-modes.js';
25
- import type { RecallUsageStore } from '../store/recall-usage.js';
26
25
  import type { MemoryLogger } from '../types.js';
27
26
  import { type OccupancyLedger } from '../util/context-occupancy.js';
28
27
  /**
@@ -90,6 +89,4 @@ export declare function registerRecall(ctx: Context, cfg: MemoryConfig, stores:
90
89
  l1: L1Store;
91
90
  scenes: Record<'chat' | 'work', SceneStore>;
92
91
  persona: Record<'chat' | 'work', PersonaStore>;
93
- }, logger: MemoryLogger, live: LiveSettingsHandle, modes: SessionModeStore, dataDir: string,
94
- /** 召回使用统计(可选;未装配 = 零成本旁路):进 topN(注入)才算 used。 */
95
- usage?: RecallUsageStore): RecallHooks;
92
+ }, logger: MemoryLogger, live: LiveSettingsHandle, modes: SessionModeStore, dataDir: string): RecallHooks;
@@ -112,9 +112,7 @@ export function emptyRecallStats(now = Date.now()) {
112
112
  updatedAt: now,
113
113
  };
114
114
  }
115
- export function registerRecall(ctx, cfg, stores, logger, live, modes, dataDir,
116
- /** 召回使用统计(可选;未装配 = 零成本旁路):进 topN(注入)才算 used。 */
117
- usage) {
115
+ export function registerRecall(ctx, cfg, stores, logger, live, modes, dataDir) {
118
116
  /** 召回去重存储(同会话已注入的记忆不再重复注入;写穿持久化,重启不丢)。 */
119
117
  const dedupe = new RecallDedupeStore(dataDir, logger);
120
118
  /** 记忆占用流水(账本迁移写穿;重启后历史会话账目由此复生)。 */
@@ -381,7 +379,6 @@ usage) {
381
379
  injectedIds: [], suppressedCount: scoped.length - fresh.length,
382
380
  durationMs: Date.now() - searchStart, outcome: 'suppressed',
383
381
  });
384
- usage?.recordAttempt(scoped.map((h) => h.id), []);
385
382
  return decision;
386
383
  }
387
384
  const lines = applyRecallBudget(fresh.map((h) => `- [${h.scene_name ? `${h.type}|${h.scene_name}` : h.type}] ${h.content}`), { maxCharsPerMemory: cfg.recall.maxCharsPerMemory, maxTotalRecallChars: cfg.recall.maxTotalRecallChars });
@@ -393,7 +390,6 @@ usage) {
393
390
  injectedIds: [], suppressedCount: scoped.length - fresh.length,
394
391
  durationMs: Date.now() - searchStart, outcome: 'suppressed',
395
392
  });
396
- usage?.recordAttempt(fresh.map((h) => h.id), []);
397
393
  return decision;
398
394
  }
399
395
  st.lastHits = lines.length;
@@ -426,8 +422,6 @@ usage) {
426
422
  injectedIds: fresh.slice(0, lines.length).map((h) => h.id), suppressedCount: scoped.length - fresh.length,
427
423
  durationMs: Date.now() - searchStart, outcome: 'injected',
428
424
  });
429
- // 使用统计:候选=scoped(尝试),进 topN(注入)=used
430
- usage?.recordAttempt(scoped.map((h) => h.id), fresh.slice(0, lines.length).map((h) => h.id));
431
425
  // 入账在成功构造注入消息之后、返回 enter 之前——任何前置抛错路径账目零扰动
432
426
  const led = ledgerFor(payload.agent.id);
433
427
  recordRecallInjection(led, text.length);
@@ -1,4 +1,22 @@
1
1
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
+ /**
3
+ * 对齐宿主 house style(dsh-agent-instructions 的 AGENTS.md 注入):user 角色
4
+ * 承载 + `<system-reminder>` 标签框。宿主对自家指令刻意**不用** system 角色——
5
+ * 会话中段的 system 消息会破坏部分聊天模板(qwen-chat-template 等),标签框即可
6
+ * 让模型把它识别为系统级元信息(Claude Code 同款语义)。
7
+ */
8
+ const SYSTEM_REMINDER_OPEN = '<system-reminder>';
9
+ const SYSTEM_REMINDER_CLOSE = '</system-reminder>';
10
+ const SLOTS_INTRO = '以下是本环境的常驻记忆槽位(激活槽位):跨会话持续生效的背景约定,仅在与之相关时作为指引遵循。' +
11
+ '它们是系统注入的元信息,不是用户发言——无需回应,请勿在回复中复述本节内容;' +
12
+ '且不覆盖系统提示与用户的直接指令,更具体的指令优先于更一般的约定。';
13
+ /**
14
+ * 槽位标题/正文来自模型工具调用,可能含有闭合标签;不转义会让槽位内容伪造
15
+ * 帧边界(宿主对指令文件正文做同样的转义)。
16
+ */
17
+ function escapeFrameBody(body) {
18
+ return body.replaceAll(SYSTEM_REMINDER_CLOSE, '<\\/system-reminder>');
19
+ }
2
20
  export function registerSlotRecall(ctx, cfg, slots, logger, _live) {
3
21
  const buildInjection = () => {
4
22
  if (!cfg.slots.enabled || !cfg.slots.inject)
@@ -13,7 +31,12 @@ export function registerSlotRecall(ctx, cfg, slots, logger, _live) {
13
31
  if (truncatedCount > 0) {
14
32
  lines.push(`… 另有 ${truncatedCount} 个常驻槽位因预算未显示,可用 memory_slot_list 查看`);
15
33
  }
16
- return '【激活槽位 · 常驻上下文】\n' + lines.join('\n');
34
+ return [
35
+ SYSTEM_REMINDER_OPEN,
36
+ SLOTS_INTRO,
37
+ escapeFrameBody(lines.join('\n')),
38
+ SYSTEM_REMINDER_CLOSE,
39
+ ].join('\n');
17
40
  };
18
41
  ctx.on('agent/pre-step', async (payload, next) => {
19
42
  const decision = await next();
package/dist/index.js CHANGED
@@ -36,8 +36,6 @@ 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 { RoomRegistryStore } from './store/rooms-registry.js';
40
- import { RecallUsageStore } from './store/recall-usage.js';
41
39
  import { registerSlotsProjection } from './projection/slots.js';
42
40
  import { errDetail, withFileLog } from './util/filelog.js';
43
41
  import { buildRouteChain, resolveModelRoute, invalidateEffortCache } from './llm.js';
@@ -160,7 +158,6 @@ export async function apply(ctx, config) {
160
158
  }
161
159
  }
162
160
  const stores = {
163
- usage: undefined, // 装配于下方(依赖 dataDir 就绪)
164
161
  l0: new L0Store(dataDir, db, embed, logger),
165
162
  l1: new L1Store(dataDir, db, embed, config.recall.strategy, logger, config.recall.decayHalfLifeDays,
166
163
  // §D 第 3 路:图谱回链。图谱不可用时 searchNodes 自带 no-op,不影响双路。
@@ -353,23 +350,16 @@ export async function apply(ctx, config) {
353
350
  backend = await createMemoryBackend({ dbPath: path.join(dataDir, 'memory.db'), dimensions: initial.dims, logger }, backend);
354
351
  }
355
352
  stores.backend = backend;
356
- // Room 注册表(分类管理 beta.4;损坏降级为纯自生长目录)
357
- const roomRegistry = new RoomRegistryStore(dataDir, logger);
358
- await roomRegistry.init();
359
353
  const ruminate = storageOk && !db.isDegraded() && ruminateFile
360
- ? new RuminateController(ctx, config, runner, stores, logger, live, ruminateFile, roomRegistry)
354
+ ? new RuminateController(ctx, config, runner, stores, logger, live, ruminateFile)
361
355
  : undefined;
362
356
  let flushL0;
363
357
  if (storageOk) {
364
358
  flushL0 = registerCapture(ctx, config, runner, stores.l0, logger, live, modes);
365
359
  }
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);
360
+ const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir);
371
361
  runner.setAfterRun(recall.invalidateProfile);
372
- registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
362
+ registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate);
373
363
  // 激活槽位(active slot):工具面 + 常驻注入 + 服务端投影(均走 ctx.effect,可撤销)
374
364
  registerSlotTools(ctx, config, stores.slots, logger, modes, live, stores.l1);
375
365
  const slotRecall = registerSlotRecall(ctx, config, stores.slots, logger, live);
@@ -388,7 +378,7 @@ export async function apply(ctx, config) {
388
378
  runnerView: (sid, mode) => runner.sessionView(sid, mode),
389
379
  l0Count: (sid) => stores.l0.countBySession(sid),
390
380
  capabilities: () => db.getCapabilities(),
391
- }, ruminate, storageOk ? roomRegistry : undefined);
381
+ }, ruminate);
392
382
  // bench 控制服务(config.benchControl 门控,默认关):仅基准/调试部署注册,
393
383
  // 供同进程的 bench-runner lifecycle 赛道触发 rebuild / 设置会话档位
394
384
  // (宿主侧 RPC 无 call(),见 bench-control.ts)
@@ -10,11 +10,6 @@
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;
18
13
  /** 同为布尔谓词(理由见 isWingId 注释)。 */
19
14
  export declare function isTag(v: unknown): boolean;
20
15
  /**
@@ -31,16 +31,6 @@ 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
- }
44
34
  /** 同为布尔谓词(理由见 isWingId 注释)。 */
45
35
  export function isTag(v) {
46
36
  return typeof v === 'string' && TAG_RE.test(v);
@@ -57,7 +47,7 @@ export function normTags(v, max = 3) {
57
47
  if (typeof raw !== 'string')
58
48
  continue;
59
49
  const t = raw.trim().toLowerCase();
60
- if (!(isTag(t) || isRoomSlug(t)) || out.includes(t))
50
+ if (!isTag(t) || out.includes(t))
61
51
  continue;
62
52
  out.push(t);
63
53
  if (out.length >= max)
@@ -66,8 +66,6 @@ export declare class RuminateController {
66
66
  private sessions;
67
67
  private totalL1;
68
68
  private pendingFile;
69
- /** Room 注册表(可选;孤儿候选预标记的合并词表来源)。 */
70
- private readonly roomRegistry;
71
69
  /** 记忆后端:未注入时包 l1(进程内,行为与改造前等价)。 */
72
70
  private backend;
73
71
  constructor(ctx: Context, cfg: MemoryConfig, runner: Pick<MemoryRunner, 'enqueue' | 'states'>, stores: {
@@ -77,15 +75,7 @@ export declare class RuminateController {
77
75
  scenes: Record<MemoryFamily, SceneStore>;
78
76
  persona: Record<MemoryFamily, PersonaStore>;
79
77
  state: StateStore;
80
- }, logger: MemoryLogger, live: LiveSettingsHandle, pendingFile: string,
81
- /** Room 注册表(可选;孤儿候选预标记的合并词表来源)。 */
82
- roomRegistry?: {
83
- listActive(): Array<{
84
- slug: string;
85
- label?: string;
86
- description?: string;
87
- }>;
88
- });
78
+ }, logger: MemoryLogger, live: LiveSettingsHandle, pendingFile: string);
89
79
  /** 状态快照 */
90
80
  getStatus(): RuminateStatus;
91
81
  /**
@@ -3,7 +3,6 @@ import { groupPendingBySession, loadPending, } from '../store/pending.js';
3
3
  import { errDetail } from '../util/filelog.js';
4
4
  import { runSceneConsolidation } from './l2.js';
5
5
  import { relabelPass } from './relabel.js';
6
- import { annotateOrphanCandidates } from '../room-review.js';
7
6
  import { runPersona } from './l3.js';
8
7
  const IDLE_STATUS = {
9
8
  running: false,
@@ -45,15 +44,11 @@ export class RuminateController {
45
44
  sessions = [];
46
45
  totalL1 = 0;
47
46
  pendingFile;
48
- /** Room 注册表(可选;孤儿候选预标记的合并词表来源)。 */
49
- roomRegistry;
50
47
  /** 记忆后端:未注入时包 l1(进程内,行为与改造前等价)。 */
51
48
  backend() {
52
49
  return this.stores.backend ?? new InProcMemoryBackend(this.stores.l1);
53
50
  }
54
- constructor(ctx, cfg, runner, stores, logger, live, pendingFile,
55
- /** Room 注册表(可选;孤儿候选预标记的合并词表来源)。 */
56
- roomRegistry) {
51
+ constructor(ctx, cfg, runner, stores, logger, live, pendingFile) {
57
52
  this.ctx = ctx;
58
53
  this.cfg = cfg;
59
54
  this.runner = runner;
@@ -61,7 +56,6 @@ export class RuminateController {
61
56
  this.logger = logger;
62
57
  this.live = live;
63
58
  this.pendingFile = pendingFile;
64
- this.roomRegistry = roomRegistry;
65
59
  }
66
60
  /** 状态快照 */
67
61
  getStatus() {
@@ -228,17 +222,6 @@ export class RuminateController {
228
222
  // 重标定失败不拖垮反刍整体(蒸馏/L2/L3 产物保留)
229
223
  this.logger.warn(`[memory] 反刍重标定失败(不影响本次产物): ${errDetail(err)}`);
230
224
  }
231
- // 孤儿候选预标记(长尾治理):无 Room 的活跃孤儿对照现有 Room 词表产出
232
- // 候选(roomReview='pending'),由 agent 用 memory_room_review 逐个复查;
233
- // 每轮至多 40 条,吞错不拖垮反刍。
234
- try {
235
- const r = await annotateOrphanCandidates(this.ctx, this.cfg, { l1: this.stores.l1, logger: this.logger, registry: this.roomRegistry }, 40);
236
- if (r.written > 0)
237
- this.status.detail = `孤儿候选预标记 ${r.written} 条(待 agent 复查,工具 memory_room_review)`;
238
- }
239
- catch (err) {
240
- this.logger.warn(`[memory] 孤儿候选预标记失败(忽略): ${errDetail(err)}`);
241
- }
242
225
  this.status.phase = 'done';
243
226
  this.finish(null);
244
227
  }
@@ -75,22 +75,17 @@ function view(state) {
75
75
  }
76
76
  /**
77
77
  * 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)。
78
+ * - **任意已提交事件都判脏**(v0.18.4 起):此前仅在 tool/result(settled、非 error)
79
+ * 时检查,实际运行中暴露出脆弱性——写入后的投影帧依赖"该会话恰好再来一条
80
+ * tool/result"才刷新;跨会话场景(会话 A 写、会话 B 看)更是要等 B 自己的下一次
81
+ * 工具调用。改为对所有事件做 O(1) revision 比较:rev 未变 → 同引用(满足 I1b,
82
+ * 无 republish thrash);rev 变 → 立即重建。写入后的下一条任意事件(用户消息、
83
+ * turn 边界、其他工具结果)都会把新帧推给客户端。
84
+ * - isError 守卫一并移除:rev 只在 store 真实变更后递增(persist 内联 RMW),
85
+ * 事件成败与否不影响"store 是否变了"这一事实;错误事件后重建读到的仍是
86
+ * 当前真实快照,不会引入错误数据。
83
87
  */
84
88
  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;
94
89
  if (store.revision() === state.rev)
95
90
  return state;
96
91
  return buildState(store);
package/dist/stats.d.ts CHANGED
@@ -3,8 +3,6 @@ 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';
8
6
  import { type LiveSettingsHandle } from './settings.js';
9
7
  import type { GraphStore } from './store/graph-store.js';
10
8
  import type { L0Store } from './store/l0.js';
@@ -25,7 +23,7 @@ export interface MemoryStatusSource {
25
23
  pending(): number;
26
24
  }
27
25
  /**
28
- * 端点全集运行时清单(与 tests/contract-keys.test.ts 的 ENDPOINTS 及
26
+ * 端点全集运行时清单(36 个,与 tests/contract-keys.test.ts 的 ENDPOINTS 及
29
27
  * contract.ts 类型映射表三方对齐,漂移由键集 diff 测试暴露)。
30
28
  * 注意:本清单同时是 HTTP 前缀路由 `/dsh-memory/rpc/<短名>` 的**放行白名单**
31
29
  * (见下方 SHORT_ENDPOINTS),漏一条 = 该端点在面板里静默消失(404 被客户端
@@ -79,9 +77,6 @@ interface MemoryRpcSources {
79
77
  rebuild?: RebuildController;
80
78
  embedManager?: EmbeddingManager;
81
79
  sessionInfo?: SessionInfoSource;
82
- /** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
83
- /** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
84
- roomRegistry?: RoomRegistryStore;
85
80
  }
86
81
  /** 端点 deps 的注入面(ctx/cfg/stores/logger 由调用方绑定,其余由此处决定)。 */
87
82
  export type EndpointDepsInput = Omit<EndpointDeps, 'ctx' | 'cfg' | 'stores' | 'logger'>;
@@ -97,25 +92,19 @@ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, store
97
92
  scenes: Record<MemoryFamily, SceneStore>;
98
93
  persona: Record<MemoryFamily, PersonaStore>;
99
94
  state: StateStore;
100
- /** 召回使用统计(可选)。 */
101
- usage?: RecallUsageStore;
102
95
  /** 记忆后端(后台边界);未装配时回退为包 l1 的进程内实现。 */
103
96
  backend?: MemoryBackend;
104
97
  /** 图谱存储(可选:未装配时图谱端点返空,不报错)。 */
105
98
  graph?: GraphStore;
106
99
  }, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager, sessionInfo?: SessionInfoSource,
107
100
  /** 反刍控制器(存储降级时为 undefined):经 buildEndpointDeps 落入 deps.ruminate。 */
108
- ruminate?: RuminateController,
109
- /** Room 分类管理依赖(注册表;缺省 = rooms-get 不带 registry、rooms-export 恒 404 语义)。 */
110
- roomRegistry?: RoomRegistryStore): void;
101
+ ruminate?: RuminateController): void;
111
102
  export interface EndpointDeps {
112
103
  ctx: Context;
113
104
  cfg: MemoryConfig;
114
105
  stores: {
115
106
  l0: L0Store;
116
107
  l1: L1Store;
117
- /** 召回使用统计(可选:未装配 = 列表不带 usage/usageSummary)。 */
118
- usage?: RecallUsageStore;
119
108
  scenes: Record<MemoryFamily, SceneStore>;
120
109
  persona: Record<MemoryFamily, PersonaStore>;
121
110
  state: StateStore;
@@ -132,15 +121,10 @@ export interface EndpointDeps {
132
121
  ruminate?: RuminateController;
133
122
  embedManager?: EmbeddingManager;
134
123
  sessionInfo?: SessionInfoSource;
135
- /** 长任务端点依赖(未装配 = 端点恒 disabled,面板不挂载)。 */
136
- /** Room 注册表(未装配 = rooms-get 不带 registry,rooms-export 不可用)。 */
137
- roomRegistry?: RoomRegistryStore;
138
124
  }
139
125
  /** 端点分发表(导出供测试直调:可精确注入 rebuild/ruminate 等可选控制器,验证 deps 接线)。 */
140
126
  export declare function handleEndpoint(endpoint: string, payload: unknown, deps: EndpointDeps): Promise<unknown>;
141
127
  /** 浏览器卡片字段(比 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;
144
128
  export declare function hitToUiRecord(r: {
145
129
  id: string;
146
130
  content: string;
@@ -157,12 +141,4 @@ export declare function hitToUiRecord(r: {
157
141
  family?: string;
158
142
  /** 退场判据:`valid_to` 闭合即已退场(软删)。由 `listL1`/`getByIds` 透传。 */
159
143
  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;
168
144
  }): UiRecord;