dsh-prime-memory 0.20.0-beta.3 → 0.20.0-beta.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 CHANGED
@@ -17,7 +17,30 @@
17
17
  - **建议状态(`longtask-hint-get`)+ 浮动球(`client/src/longtask-fab.ts`)**:ego-browser 式 vanilla overlay(FAB+面板,拖拽/位置持久化/视口夹紧),**建议不阻断**——仅当「上下文占用达 `contextThresholdPct`」或「todo 漂移达 `driftThreshold`」且长任务未开启时点亮呼吸光晕;面板内:长任务开关(临时,per-session,**不带 mode**——切开关不重置用户档位)、占用/漂移进度条、手动尾部压缩按钮(长任务开启才可用)、任务计数。轮询忙 2s/闲 5s,RPC 失败静默;座位契约不变(settings.section 恰一个,body 单例不占任何座位)。
18
18
  - **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
19
19
 
20
- ## [0.20.0-beta.3] — 2026-10-01
20
+ ## [0.20.0-beta.5] — 2026-10-01
21
+
22
+ > Room 分类管理第二船(**破坏性面**)。dryRun 默认 true——merge/rename 先预览影响面,确认后才实跑;执行前自动备份到 `rooms-merge-backups/`。
23
+
24
+ ### 新增
25
+
26
+ - **Room 注册表两级制**:slug 支持 `major` 或 `major/minor` 两级(如 `dsh-plugin`、`dsh-plugin/merge`);**大类上限 200**,小类细分不占大类额度(总量保险 2000)。isTag 词表不放宽(slash 仅注册表层允许)。
27
+ - **merge/rename/retire(工具 `memory_room_admin`,高权限)**:merge 游标重写全部匹配记录的 tags(写前重读-合并-写回,roomCandidates/roomReview/hall/anchors 等键保全;含已退场行,与计数口径一致);rename = merge 1:1 + 注册表改名(aliases 收旧名);retire 仅词表退役(存量不动)。merge/rename 与反刍并发会相互覆盖——请错峰执行(工具提示与 ADR-0020 均已声明)。
28
+ - **重写安全三件套**:dryRun 默认只返 affected+预览零写入;执行前自动备份匹配记录到 `rooms-merge-backups/<ts>-<slug>.json`;完成自动 `invalidateRooms` + 按受影响 (family, scene) 入队场景重算(`source='room-merge'`)。
29
+ - **面板 Room 管理子面板**(高权限开启后出现):注册表单(slug/名称/说明,走新 RPC `room-register`)、孤儿清单导出;合并/退役建议走 agent 工具(dryRun 预览更详细)。
30
+ - 端点面 46 → 47(`room-register`)。
31
+
32
+ ## [0.20.0-beta.4] — 2026-10-01## [0.20.0-beta.4] — 2026-10-01
33
+
34
+ > Room 分类管理第一船(**零漂移面**):注册表空 = 与 beta.3 行为逐字一致。merge/rename/retire 破坏性面将随 beta.5(dryRun 默认 true 保留一版)。
35
+
36
+ ### 新增
37
+
38
+ - **Room 注册表(`rooms-registry.json` sidecar)**:预先注册策划好的 Room(slug/label/description);损坏/为空 → 降级回退纯自生长目录,绝不阻塞面板与标注器;上限 200 防刷。`rooms-get` 端点层合并:注册条目 count=0 也进词表并带 `source`/`label`,响应新增 `registry` 全量字段(客户端可选读)。
39
+ - **导出端点 `dsh-memory/rooms-export`**(kind=rooms|orphans,CSV 直出):rooms = 注册表∪自生长词表带计数;orphans = 无 Room 绑定的活跃记录清单(含候选/复查状态列)。
40
+ - **工具 `memory_room_admin`**(action=list|register):list 列注册表+自生长总览;register 预注册 Room(高权限门,幂等补全)——配合 `memory_room_review` 的逐个复查,agent 有了「注册→预标记→逐个确认」的完整分类管理入口。
41
+ - 客户端:Room chip 带「预注册/自生长」来源标示;孤儿 chip 带待复查语义。
42
+
43
+ ## [0.20.0-beta.3] — 2026-10-01## [0.20.0-beta.3] — 2026-10-01
21
44
 
22
45
  ### 新增
23
46
 
package/dist/client.js CHANGED
@@ -3298,6 +3298,7 @@ var __defProp = Object.defineProperty;
3298
3298
  });
3299
3299
  const [roomPage, setRoomPage] = (0, import_react15.useState)(0);
3300
3300
  const [orphanCount, setOrphanCount] = (0, import_react15.useState)(0);
3301
+ const [roomRegistry, setRoomRegistry] = (0, import_react15.useState)([]);
3301
3302
  const [retiredFilter, setRetiredFilter] = (0, import_react15.useState)("");
3302
3303
  const [rooms, setRooms] = (0, import_react15.useState)([]);
3303
3304
  const [wingCatalog, setHallCatalog] = (0, import_react15.useState)(null);
@@ -3356,6 +3357,7 @@ var __defProp = Object.defineProperty;
3356
3357
  if (r && r.ok) {
3357
3358
  setRooms(r.value.rooms ?? []);
3358
3359
  setOrphanCount(r.value.orphanCount ?? 0);
3360
+ setRoomRegistry(r.value.registry ?? []);
3359
3361
  }
3360
3362
  }).catch(() => {
3361
3363
  });
@@ -3562,6 +3564,35 @@ var __defProp = Object.defineProperty;
3562
3564
  ]
3563
3565
  }
3564
3566
  ),
3567
+ rooms.length > 0 || orphanCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3568
+ "button",
3569
+ {
3570
+ type: "button",
3571
+ title: "导出分类数据 CSV(Room 词表+计数)",
3572
+ onClick: () => {
3573
+ rpc("dsh-memory/rooms-export", { kind: "rooms" }).then((r) => {
3574
+ if (!r || !r.ok || !r.value?.csv) return;
3575
+ const blob = new Blob([r.value.csv], { type: "text/csv;charset=utf-8" });
3576
+ const url = URL.createObjectURL(blob);
3577
+ const a = document.createElement("a");
3578
+ a.href = url;
3579
+ a.download = "dsh-memory-rooms.csv";
3580
+ a.click();
3581
+ URL.revokeObjectURL(url);
3582
+ }).catch(() => void 0);
3583
+ },
3584
+ style: {
3585
+ cursor: "pointer",
3586
+ fontSize: 12,
3587
+ padding: "2px 8px",
3588
+ borderRadius: 6,
3589
+ border: "1px solid var(--dsh-mem-border)",
3590
+ background: "transparent",
3591
+ color: "var(--dsh-mem-text-2)"
3592
+ },
3593
+ children: "导出 CSV"
3594
+ }
3595
+ ) : null,
3565
3596
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3566
3597
  "button",
3567
3598
  {
@@ -3612,7 +3643,7 @@ var __defProp = Object.defineProperty;
3612
3643
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: roomsOpen ? 6 : 0 }, children: [
3613
3644
  (roomsOpen ? rooms.slice(roomPage * ROOM_PAGE_SIZE, (roomPage + 1) * ROOM_PAGE_SIZE) : rooms.slice(0, 8)).map((r) => {
3614
3645
  const on = tagFilter === r.room && !untaggedOnly;
3615
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3646
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3616
3647
  "button",
3617
3648
  {
3618
3649
  type: "button",
@@ -3634,7 +3665,10 @@ var __defProp = Object.defineProperty;
3634
3665
  background: on ? "var(--dsh-mem-bg-inset)" : "transparent",
3635
3666
  color: on ? "var(--dsh-mem-accent)" : "var(--dsh-mem-text-2)"
3636
3667
  },
3637
- children: r.room + " · " + r.count
3668
+ children: [
3669
+ r.label ? r.label + " · " + r.count : r.room + " · " + r.count,
3670
+ r.source === "pre-registered" ? " ⭐" : ""
3671
+ ]
3638
3672
  },
3639
3673
  r.room
3640
3674
  );
@@ -3677,7 +3711,54 @@ var __defProp = Object.defineProperty;
3677
3711
  children: "▶"
3678
3712
  }
3679
3713
  )
3680
- ] }) : null
3714
+ ] }) : null,
3715
+ roomsOpen && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { marginTop: 8, paddingTop: 8, borderTop: "1px solid var(--dsh-mem-border)" }, children: [
3716
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [
3717
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "管理(高权限)" }),
3718
+ !hiPriv && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "需先开启高权限模式(见上方工具栏)" })
3719
+ ] }),
3720
+ hiPriv && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center" }, children: [
3721
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { "data-adm-slug": true, placeholder: "slug(如 dsh-plugin 或 dsh-plugin/merge)", style: { ...S.input, width: 150, fontSize: 12 } }),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { "data-adm-label": true, placeholder: "名称(可中文)", style: { ...S.input, width: 110, fontSize: 12 } }),
3723
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { "data-adm-desc": true, placeholder: "归类说明(喂标注器)", style: { ...S.input, width: 170, fontSize: 12 } }),
3724
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3725
+ "button",
3726
+ {
3727
+ type: "button",
3728
+ onClick: async () => {
3729
+ const q = (sel2) => document.querySelector(sel2)?.value ?? "";
3730
+ const r = await rpc("dsh-memory/room-register", { slug: q("[data-adm-slug]"), label: q("[data-adm-label]"), description: q("[data-adm-desc]") }).catch(() => null);
3731
+ void r;
3732
+ loadRooms();
3733
+ },
3734
+ style: { cursor: "pointer", fontSize: 12, padding: "3px 10px", borderRadius: 6, border: "1px solid var(--dsh-mem-accent)", background: "transparent", color: "var(--dsh-mem-accent)" },
3735
+ children: "注册"
3736
+ }
3737
+ ),
3738
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3739
+ "button",
3740
+ {
3741
+ type: "button",
3742
+ title: "导出无 Room 绑定的孤儿清单(含候选)",
3743
+ onClick: () => {
3744
+ rpc("dsh-memory/rooms-export", { kind: "orphans" }).then((r) => {
3745
+ if (!r || !r.ok || !r.value?.csv) return;
3746
+ const blob = new Blob([r.value.csv], { type: "text/csv;charset=utf-8" });
3747
+ const url = URL.createObjectURL(blob);
3748
+ const a = document.createElement("a");
3749
+ a.href = url;
3750
+ a.download = "dsh-memory-room-orphans.csv";
3751
+ a.click();
3752
+ URL.revokeObjectURL(url);
3753
+ }).catch(() => void 0);
3754
+ },
3755
+ style: { cursor: "pointer", fontSize: 12, padding: "3px 10px", borderRadius: 6, border: "1px solid var(--dsh-mem-border)", background: "transparent", color: "var(--dsh-mem-text-2)" },
3756
+ children: "导出孤儿"
3757
+ }
3758
+ ),
3759
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "合并/改名/退役:用 agent 工具 memory_room_admin(merge/rename/retire,dryRun 默认预览)" })
3760
+ ] })
3761
+ ] })
3681
3762
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: S.muted, children: "Room 分类:暂无(由反刍涌现的标签自动生成,无需手工建立)" })
3682
3763
  ] }),
3683
3764
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
@@ -557,12 +557,51 @@ export interface WingOverviewResponse {
557
557
  * 降级/无 tags 时 `rooms` 为空数组(面板显示"暂无"),不报错。
558
558
  */
559
559
  export interface RoomsGetResponse {
560
- /** Room 列表(按记录数降序,同数按名升序)。 */
560
+ /** Room 列表(按记录数降序,同数按名升序;注册表条目 count=0 并带 source/label)。
561
+ * 条目可选字段 source/label 由端点层合并注册表后附带(客户端可选读,零断裂)。 */
561
562
  rooms: RoomCount[];
562
563
  /** Room 总数(= rooms.length,冗余只为前端少写一次 .length)。 */
563
564
  total: number;
564
565
  /** 孤儿记忆数:没有任何 Room(tags 为空/缺失)的记录条数(与 rooms 同口径含已退场)。 */
565
566
  orphanCount: number;
567
+ /** 注册表条目(全量含 retired;分类管理面板用;未装配 = undefined)。 */
568
+ registry?: Array<{
569
+ slug: string;
570
+ label?: string;
571
+ description?: string;
572
+ source: 'pre-registered' | 'grown';
573
+ aliases?: string[];
574
+ status: 'active' | 'retired';
575
+ }>;
576
+ }
577
+ /** dsh-memory/room-register(预注册 Room;高权限:面板 memoryMutate 开启才生效)。 */
578
+ export interface RoomRegisterRequest {
579
+ slug: string;
580
+ label?: string;
581
+ description?: string;
582
+ }
583
+ export interface RoomRegisterResponse {
584
+ slug: string;
585
+ created: boolean;
586
+ /** 幂等补全时的提示(如「已存在,只补全缺失字段」)。 */
587
+ notice?: string;
588
+ }
589
+ /** dsh-memory/rooms-export(分类数据导出,CSV 直出)。 */
590
+ export interface RoomsExportRequest {
591
+ /** rooms = 词表+计数;orphans = 无 Room 绑定的记录清单。 */
592
+ kind: 'rooms' | 'orphans';
593
+ /** orphans 上限(1..10000,默认 2000)。 */
594
+ limit?: number;
595
+ }
596
+ export interface RoomsExportResponse {
597
+ kind: 'rooms' | 'orphans';
598
+ /** CSV 文本(首行表头;
599
+ 行尾;RFC 4180 转义)。 */
600
+ csv: string;
601
+ /** 数据行数(不含表头)。 */
602
+ total: number;
603
+ /** truncated = 达到 limit 上限,结果可能不完整。 */
604
+ truncated: boolean;
566
605
  }
567
606
  /** dsh-memory/wing-backfill(一键回填,后台任务;端点立即返回,进度以 wing-overview 轮询)。 */
568
607
  export interface WingBackfillResponse {
@@ -1260,6 +1299,8 @@ export interface DshMemoryRequestMap {
1260
1299
  'dsh-memory/longtask-compress-tail': LongTaskCompressTailRequest;
1261
1300
  'dsh-memory/wing-overview': Record<string, never>;
1262
1301
  'dsh-memory/rooms-get': Record<string, never>;
1302
+ 'dsh-memory/rooms-export': RoomsExportRequest;
1303
+ 'dsh-memory/room-register': RoomRegisterRequest;
1263
1304
  'dsh-memory/wing-backfill': Record<string, never>;
1264
1305
  'dsh-memory/session-stats': SessionStatsRequest;
1265
1306
  'dsh-memory/settings-get': Record<string, never>;
@@ -1307,6 +1348,8 @@ export interface DshMemoryResponseMap {
1307
1348
  'dsh-memory/session-mode-set': SessionModeSetResponse;
1308
1349
  'dsh-memory/wing-overview': WingOverviewResponse;
1309
1350
  'dsh-memory/rooms-get': RoomsGetResponse;
1351
+ 'dsh-memory/rooms-export': RoomsExportResponse;
1352
+ 'dsh-memory/room-register': RoomRegisterResponse;
1310
1353
  'dsh-memory/wing-backfill': WingBackfillResponse;
1311
1354
  'dsh-memory/session-stats': SessionStatsResponse;
1312
1355
  'dsh-memory/settings-get': SettingsGetResponse;
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ import { registerSlotRecall } from './hooks/slot-recall.js';
39
39
  import { registerLongTaskMonitor } from './hooks/long-task.js';
40
40
  import { ContextUsageStore } from './store/context-usage.js';
41
41
  import { TodoRefStore } from './store/todo-ref.js';
42
+ import { RoomRegistryStore } from './store/rooms-registry.js';
42
43
  import { registerSlotsProjection } from './projection/slots.js';
43
44
  import { errDetail, withFileLog } from './util/filelog.js';
44
45
  import { buildRouteChain, resolveModelRoute, invalidateEffortCache } from './llm.js';
@@ -364,6 +365,9 @@ export async function apply(ctx, config) {
364
365
  // todo/write 快照参考(仅长任务会话;写 pinned 槽位走 slot-recall 常驻注入)。
365
366
  const contextUsage = new ContextUsageStore(logger);
366
367
  const todoRef = new TodoRefStore();
368
+ // Room 注册表(分类管理 beta.4;损坏降级为纯自生长目录)
369
+ const roomRegistry = new RoomRegistryStore(dataDir, logger);
370
+ await roomRegistry.init();
367
371
  if (storageOk) {
368
372
  registerLongTaskMonitor(ctx, {
369
373
  cfg: config,
@@ -376,7 +380,7 @@ export async function apply(ctx, config) {
376
380
  }
377
381
  const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir);
378
382
  runner.setAfterRun(recall.invalidateProfile);
379
- registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate);
383
+ registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
380
384
  // 激活槽位(active slot):工具面 + 常驻注入 + 服务端投影(均走 ctx.effect,可撤销)
381
385
  registerSlotTools(ctx, config, stores.slots, logger, modes, live, stores.l1);
382
386
  const slotRecall = registerSlotRecall(ctx, config, stores.slots, logger, live);
@@ -395,7 +399,7 @@ export async function apply(ctx, config) {
395
399
  runnerView: (sid, mode) => runner.sessionView(sid, mode),
396
400
  l0Count: (sid) => stores.l0.countBySession(sid),
397
401
  capabilities: () => db.getCapabilities(),
398
- }, ruminate, storageOk ? { runner, slots: stores.slots, contextUsage, todoRef } : undefined);
402
+ }, ruminate, storageOk ? { runner, slots: stores.slots, contextUsage, todoRef } : undefined, storageOk ? roomRegistry : undefined);
399
403
  // bench 控制服务(config.benchControl 门控,默认关):仅基准/调试部署注册,
400
404
  // 供同进程的 bench-runner lifecycle 赛道触发 rebuild / 设置会话档位
401
405
  // (宿主侧 RPC 无 call(),见 bench-control.ts)
@@ -4,6 +4,7 @@ import { errDetail } from '../util/filelog.js';
4
4
  import { runSceneConsolidation } from './l2.js';
5
5
  import { relabelPass } from './relabel.js';
6
6
  import { processSceneReclusterJobs } from './recluster.js';
7
+ import { annotateOrphanCandidates } from '../room-review.js';
7
8
  import { runPersona } from './l3.js';
8
9
  const IDLE_STATUS = {
9
10
  running: false,
@@ -233,6 +234,17 @@ export class RuminateController {
233
234
  catch (err) {
234
235
  this.logger.warn(`[memory] 场景重聚类调度失败(忽略): ${errDetail(err)}`);
235
236
  }
237
+ // 孤儿候选预标记(长尾治理):无 Room 的活跃孤儿对照现有 Room 词表产出
238
+ // 候选(roomReview='pending'),由 agent 用 memory_room_review 逐个复查;
239
+ // 每轮至多 40 条,吞错不拖垮反刍。
240
+ try {
241
+ const r = await annotateOrphanCandidates(this.ctx, this.cfg, { l1: this.stores.l1, logger: this.logger }, 40);
242
+ if (r.written > 0)
243
+ this.status.detail = `孤儿候选预标记 ${r.written} 条(待 agent 复查,工具 memory_room_review)`;
244
+ }
245
+ catch (err) {
246
+ this.logger.warn(`[memory] 孤儿候选预标记失败(忽略): ${errDetail(err)}`);
247
+ }
236
248
  this.status.phase = 'done';
237
249
  this.finish(null);
238
250
  }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * 孤儿记忆的 Room 候选预标记 + agent 逐个复查(长尾治理)。
3
+ *
4
+ * 背景:Room 由 metadata.tags 自生长,但 tags 只在反刍 relabel 段随 wing 补打
5
+ * 顺带产出(每轮 ≤40 条、且只覆盖本轮补 wing 的记录)——「有 wing 无 tags」与
6
+ * 「预算用尽被 deferred」的记录成为无 Room 绑定的孤儿,且标注器不参考现有
7
+ * Room 词表,产出碎片化新 slug(81 个 Room 大多 1 条的成因)。
8
+ *
9
+ * 两段式:
10
+ * 1. **预标记(annotateOrphanCandidates)**:抽取活跃孤儿,LLM 对照**现有 Room
11
+ * 词表**产出候选(优先挂靠现有 Room,≤2 个新 slug 兜底),写
12
+ * `metadata.roomCandidates` + `roomReview:'pending'`——不动 tags、不进
13
+ * Room、不进召回,只是"待确认的建议"。
14
+ * 2. **逐个复查(memory_room_review 工具)**:agent 取下一条待复查记录
15
+ * (带候选 + 现有 Room 词表),确认后 confirm 写入 tags(进 Room,高权限
16
+ * 门控),或 skip 跳过。确认/跳过都是显式动作,预标记本身零行为面。
17
+ *
18
+ * 存储即 metadata 保留键(与 relabel 写 tags 同款 patchMetadata 合并纪律,
19
+ * 写前重读):`roomCandidates` / `roomReview:'pending'|'confirmed'|'skipped'`。
20
+ * 两键不在 rooms 聚合(rooms 只读 $.tags)、不在快照哈希投影关注的治理列——
21
+ * 与既有 metadata 键同层,无 schema 变更。
22
+ */
23
+ import type { Context } from '@deepseek-ai/cordis';
24
+ import type { MemoryConfig } from './config.js';
25
+ import type { MemoryLogger, MemoryRecord } from './types.js';
26
+ export declare const ROOM_CANDIDATES_KEY = "roomCandidates";
27
+ export declare const ROOM_REVIEW_KEY = "roomReview";
28
+ export type RoomReviewState = 'pending' | 'confirmed' | 'skipped';
29
+ /** 复查与候选操作对 L1 的最小依赖面(便于测试注入)。 */
30
+ export interface RoomReviewStore {
31
+ list(opts: {
32
+ untagged?: boolean;
33
+ retired?: boolean;
34
+ limit: number;
35
+ offset: number;
36
+ }): {
37
+ items: MemoryRecord[];
38
+ total: number;
39
+ };
40
+ getByIds(ids: readonly string[]): MemoryRecord[];
41
+ patchMetadata(id: string, metadata: Record<string, unknown>): boolean;
42
+ listRooms(): Array<{
43
+ room: string;
44
+ count: number;
45
+ }>;
46
+ /** 确认写 tags 后失效 Room 计数缓存(30s TTL)。 */
47
+ invalidateRooms(): void;
48
+ }
49
+ /** merge/rename 的 store 面(RoomReviewStore + 游标重写与备份;L1Store 已实现)。 */
50
+ export interface RoomMergeStore extends RoomReviewStore {
51
+ listByTagAll(tag: string, cap?: number): MemoryRecord[];
52
+ backupTagRecords(tag: string): {
53
+ file: string;
54
+ count: number;
55
+ };
56
+ rewriteTag(from: string, to: string, cap?: number): {
57
+ rewritten: number;
58
+ scanned: number;
59
+ families: string[];
60
+ scenes: string[];
61
+ hasMore: boolean;
62
+ };
63
+ }
64
+ /** Room 注册表最小面(merge 收尾;RoomRegistryStore 已满足)。 */
65
+ export interface RoomRegistryMerge {
66
+ markMerged(fromSlug: string, toSlug: string): Promise<boolean>;
67
+ renameSlug(oldSlug: string, newSlug: string): Promise<boolean>;
68
+ }
69
+ export interface RoomCandidate {
70
+ id: string;
71
+ rooms: string[];
72
+ }
73
+ /** 解析标注器输出的逐条候选(容错:id 配对失败丢弃;isTag 校验;目录内优先,≤3)。 */
74
+ export declare function parseRoomCandidateItems(parsed: ReadonlyArray<{
75
+ id: string;
76
+ rooms: readonly string[];
77
+ }>, ids: ReadonlyArray<string>, roomCatalog: ReadonlyArray<{
78
+ room: string;
79
+ count: number;
80
+ }>): RoomCandidate[];
81
+ /** 候选标注系统 prompt:喂现有 Room 词表(带计数),优先挂靠、少量新建兜底。 */
82
+ export declare function roomCandidateSystemPrompt(roomCatalog: ReadonlyArray<{
83
+ room: string;
84
+ count: number;
85
+ }>): string;
86
+ /** 候选标注器:一批孤儿记录 → 候选 Room(优先现有词表;失败返回空,调用方零改动)。 */
87
+ export declare function roomCandidateChunk(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, chunk: MemoryRecord[], roomCatalog: ReadonlyArray<{
88
+ room: string;
89
+ count: number;
90
+ }>): Promise<Array<{
91
+ id: string;
92
+ rooms: string[];
93
+ }>>;
94
+ export interface OrphanAnnotateIO {
95
+ l1: RoomReviewStore;
96
+ logger: MemoryLogger;
97
+ }
98
+ /**
99
+ * 预标记消费器:活跃孤儿(≤limit 条)批量产候选并写 pending。
100
+ * 写前重读合并(与 relabel 同纪律);失败单条跳过、原记录零改动。
101
+ */
102
+ export interface OrphanAnnotateOverrides {
103
+ /** 测试注入口:替换 LLM 候选标注器(默认走真实 callLLM 路径)。 */
104
+ chunkAnnotator?: (chunk: MemoryRecord[]) => Promise<Array<{
105
+ id: string;
106
+ rooms: string[];
107
+ }>>;
108
+ }
109
+ export declare function annotateOrphanCandidates(ctx: Context, cfg: MemoryConfig, io: OrphanAnnotateIO, limit?: number, overrides?: OrphanAnnotateOverrides): Promise<{
110
+ selected: number;
111
+ candidates: number;
112
+ written: number;
113
+ }>;
114
+ export interface MergeRoomResult {
115
+ dryRun: boolean;
116
+ /** 影响条数(dryRun=预览数;实跑=实际重写数)。 */
117
+ affected: number;
118
+ preview: string[];
119
+ applied: number;
120
+ hasMore: boolean;
121
+ backupFile?: string;
122
+ /** 实跑时回传:受影响的族与场景(调用方入队 recluster 'room-merge')。 */
123
+ families?: string[];
124
+ scenes?: string[];
125
+ }
126
+ /**
127
+ * Room merge/rename 编排(破坏性动作,分类管理 beta.5):
128
+ * - dryRun(缺省 true):只统计 affected + 前 6 条预览,**零写入**;
129
+ * - 实跑:执行前备份(matching rows → rooms-merge-backups/)→ 游标重写
130
+ * (写前重读-合并-写回,其余 metadata 键保全)→ 注册表 markMerged/renameSlug
131
+ * → 返回受影响 (family, scene) 供调用方入队 recluster('room-merge');
132
+ * - 单飞:并发 merge 直接抛错;与 relabel/ruminate 的时间错开由调用方保证。
133
+ * 抛错语义:非法参数/注册表冲突直接抛(调用方转 notice)。
134
+ */
135
+ export declare function mergeRoom(io: {
136
+ l1: RoomMergeStore;
137
+ registry: RoomRegistryMerge;
138
+ logger: MemoryLogger;
139
+ }, from: string, to: string, opts?: {
140
+ dryRun?: boolean;
141
+ cap?: number;
142
+ }): Promise<MergeRoomResult>;
143
+ /** rename = merge 1:1 + 注册表改名(冲突时抛错提示走 merge)。 */
144
+ export declare function renameRoom(io: {
145
+ l1: RoomMergeStore;
146
+ registry: RoomRegistryMerge;
147
+ logger: MemoryLogger;
148
+ }, from: string, to: string, opts?: {
149
+ dryRun?: boolean;
150
+ cap?: number;
151
+ }): Promise<MergeRoomResult>;
152
+ /** 下一条待复查(供 agent 逐个过):记录摘要 + 候选 + 现有 Room 词表 + 剩余数。 */
153
+ export declare function nextReview(l1: RoomReviewStore): {
154
+ id: string;
155
+ content: string;
156
+ type: string;
157
+ sceneName: string;
158
+ candidates: string[];
159
+ roomCatalog: Array<{
160
+ room: string;
161
+ count: number;
162
+ }>;
163
+ remaining: number;
164
+ } | null;
165
+ /** 待复查剩余数(面板/工具兜底信息)。 */
166
+ export declare function pendingReviewCount(l1: RoomReviewStore): number;
167
+ /**
168
+ * 确认复查结果:候选(或 agent 改写的)写入 metadata.tags(**进 Room**),
169
+ * 清候选、标 confirmed。**高权限门控由调用方执行**(与 memory_delete 同款)。
170
+ * 返回写入的 tags;id 不存在/状态非法返回 null。
171
+ */
172
+ export declare function confirmReview(l1: RoomReviewStore, id: string, rooms: readonly string[]): {
173
+ tags: string[];
174
+ } | null;
175
+ /** 跳过:标 skipped(不再进 pending 队列)。已 confirmed 的记录不受 skip 影响(挂了 Room 的不算长尾)。 */
176
+ export declare function skipReview(l1: RoomReviewStore, id: string): boolean;