dsh-prime-memory 0.16.1-beta.0 → 0.16.1-beta.1

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
@@ -58,6 +58,10 @@
58
58
 
59
59
  ## [0.16.1] — 2026-09-24
60
60
 
61
+ ### 新增
62
+
63
+ - **记录面板「退场筛查」**:软删记录按设计不隐藏(可恢复),但与活跃记录混排不便分辨——现在列表工具行加了三态筛选「**全部 / 仅活跃 / 仅退场**」。后端 `listL1` 补同口径三态过滤(缺省全量,快照/重建不受影响;`retired:true` 与 `listRetiredL1` 同一判据,两份视图看到同一批行);契约 `ListRecordsRequest.retired?: boolean`。筛查仅浏览路径生效——关键词检索只覆盖检索面,已退场记录本就不在其中。「加载更多」与删除/恢复后的自动刷新都会保持当前筛查态。
64
+
61
65
  ### 修复
62
66
 
63
67
  - **面板「删除记忆」确认后看似毫无反应——退场态没有透传给 UI。** 软删(retire)在服务端一直正常(`valid_to` 闭合 + 撤出检索面 + 可恢复),但按设计活动列表**保留**退场记录(`l1-retire` 测试钉死「面板浏览路径不隐藏退场记录」),而 `UiRecord` 契约里根本没有"是否已退场"字段、活动列表的记录行也没有任何视觉差异,"已退场"区又默认折叠——用户确认删除后看到的是**一条一模一样的记录**,自然以为"删除没生效"。
package/dist/client.js CHANGED
@@ -3226,6 +3226,11 @@ var __defProp = Object.defineProperty;
3226
3226
  // client/src/tabs/RecordsTab.tsx
3227
3227
  var import_react15 = require("react");
3228
3228
  var import_jsx_runtime15 = require("react/jsx-runtime");
3229
+ function retiredSelOf(r) {
3230
+ if (r === "active") return false;
3231
+ if (r === "retired") return true;
3232
+ return void 0;
3233
+ }
3229
3234
  var TYPE_CHOICES = [
3230
3235
  "persona",
3231
3236
  "episodic",
@@ -3255,9 +3260,10 @@ var __defProp = Object.defineProperty;
3255
3260
  const [sceneFilter, setSceneFilter] = (0, import_react15.useState)("");
3256
3261
  const [hallFilter, setHallFilter] = (0, import_react15.useState)([]);
3257
3262
  const [tagFilter, setTagFilter] = (0, import_react15.useState)("");
3263
+ const [retiredFilter, setRetiredFilter] = (0, import_react15.useState)("");
3258
3264
  const [rooms, setRooms] = (0, import_react15.useState)([]);
3259
3265
  const [wingCatalog, setHallCatalog] = (0, import_react15.useState)(null);
3260
- const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "" });
3266
+ const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" });
3261
3267
  const seqRef = (0, import_react15.useRef)(0);
3262
3268
  const fetchPage = (0, import_react15.useCallback)(
3263
3269
  (conds, offset, append) => {
@@ -3270,6 +3276,8 @@ var __defProp = Object.defineProperty;
3270
3276
  if (conds.scene) payload.scene = conds.scene;
3271
3277
  if (conds.halls.length > 0) payload.halls = conds.halls;
3272
3278
  if (conds.tag) payload.tag = conds.tag;
3279
+ const retiredSel = retiredSelOf(conds.retired);
3280
+ if (retiredSel !== void 0) payload.retired = retiredSel;
3273
3281
  rpc("dsh-memory/list-records", payload).then((r) => {
3274
3282
  if (token !== seqRef.current) return;
3275
3283
  setLoading(false);
@@ -3294,7 +3302,13 @@ var __defProp = Object.defineProperty;
3294
3302
  [rpc]
3295
3303
  );
3296
3304
  const search = () => {
3297
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter };
3305
+ const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: retiredFilter };
3306
+ setLast(conds);
3307
+ fetchPage(conds, 0, false);
3308
+ };
3309
+ const applyRetiredFilter = (next) => {
3310
+ setRetiredFilter(next);
3311
+ const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: next };
3298
3312
  setLast(conds);
3299
3313
  fetchPage(conds, 0, false);
3300
3314
  };
@@ -3305,7 +3319,7 @@ var __defProp = Object.defineProperty;
3305
3319
  });
3306
3320
  }, [rpc]);
3307
3321
  (0, import_react15.useEffect)(() => {
3308
- fetchPage({ query: "", type: "", scene: "", halls: [], tag: "" }, 0, false);
3322
+ fetchPage({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" }, 0, false);
3309
3323
  loadRooms();
3310
3324
  }, [fetchPage, loadRooms]);
3311
3325
  const loadHiPriv = (0, import_react15.useCallback)(() => {
@@ -3486,7 +3500,7 @@ var __defProp = Object.defineProperty;
3486
3500
  onClick: () => {
3487
3501
  const next = on ? "" : r.room;
3488
3502
  setTagFilter(next);
3489
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: next };
3503
+ const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: next, retired: retiredFilter };
3490
3504
  setLast(conds);
3491
3505
  fetchPage(conds, 0, false);
3492
3506
  },
@@ -3508,6 +3522,29 @@ var __defProp = Object.defineProperty;
3508
3522
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { ...S.muted, marginBottom: 10 }, children: "Room 分类:暂无(由反刍涌现的标签自动生成,无需手工建立)" }),
3509
3523
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
3510
3524
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: loading ? "加载中…" : countText }),
3525
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "退场筛查" }),
3526
+ [["", "全部"], ["active", "仅活跃"], ["retired", "仅退场"]].map(([val, label]) => {
3527
+ const on = retiredFilter === val;
3528
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3529
+ "button",
3530
+ {
3531
+ type: "button",
3532
+ title: val === "" ? "活跃与已退场混排(已退场的带徽标)" : val === "active" ? "只看未退场的记忆" : "只看已退场(软删)的记忆,可在此恢复",
3533
+ onClick: () => applyRetiredFilter(val),
3534
+ style: {
3535
+ cursor: "pointer",
3536
+ fontSize: 12,
3537
+ padding: "2px 10px",
3538
+ borderRadius: 999,
3539
+ border: on ? "1px solid var(--dsh-mem-accent)" : "1px solid var(--dsh-mem-border)",
3540
+ background: on ? "var(--dsh-mem-bg-inset)" : "transparent",
3541
+ color: on ? "var(--dsh-mem-accent)" : "var(--dsh-mem-text-2)"
3542
+ },
3543
+ children: label
3544
+ },
3545
+ val
3546
+ );
3547
+ }),
3511
3548
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3512
3549
  NButton,
3513
3550
  {
@@ -650,6 +650,12 @@ export interface ListRecordsRequest {
650
650
  * Room 由 tags 派生(自生长),故这里传的是**具体 tag 名**而非枚举 id。
651
651
  */
652
652
  tag?: string;
653
+ /**
654
+ * 退场(软删)筛查:三态。省略 = 全部(活跃+已退场混排,靠 `UiRecord.retired`
655
+ * 徽标区分);`false` = 仅活跃;`true` = 仅已退场。
656
+ * **仅浏览路径生效**:关键词检索只覆盖检索面,已退场记录本就不在其中。
657
+ */
658
+ retired?: boolean;
653
659
  /** 1~200,默认 50。 */
654
660
  limit?: number;
655
661
  /** 0~1_000_000。 */
package/dist/stats.js CHANGED
@@ -746,6 +746,9 @@ export async function handleEndpoint(endpoint, payload, deps) {
746
746
  const p = (payload ?? {});
747
747
  if (p.query !== undefined && p.query.length > 4096)
748
748
  throw new Error('query 过长(≤4096 字符)');
749
+ // 退场筛查:三态(缺省=全部混排/false=仅活跃/true=仅已退场)。显式布尔才透传,
750
+ // 其余值(字符串/数字等)一律视为缺省——宁可宽看,不可静默筛掉一半。
751
+ const retiredSel = typeof p.retired === 'boolean' ? p.retired : undefined;
749
752
  // Room 过滤:tag 是**自生长**的 slug(无枚举),只做形状与长度校验(SQL 侧参数化)
750
753
  const tagSel = typeof p.tag === 'string' ? p.tag.trim().slice(0, 64) : '';
751
754
  if (tagSel && !isTag(tagSel))
@@ -803,7 +806,9 @@ export async function handleEndpoint(endpoint, payload, deps) {
803
806
  };
804
807
  return resp;
805
808
  }
806
- const { items, total } = stores.l1.list({ type: p.type || undefined, scene: p.scene || undefined, hall: p.hall || undefined, halls: wingSel.length > 0 ? wingSel : undefined, tag: tagSel || undefined, limit, offset });
809
+ // 退场筛查仅浏览路径透传;检索路径不接——FTS/向量里本就没有已退场行,
810
+ // 接了只会让「仅退场+关键词」永远空结果,不如如实不筛。
811
+ const { items, total } = stores.l1.list({ type: p.type || undefined, scene: p.scene || undefined, hall: p.hall || undefined, halls: wingSel.length > 0 ? wingSel : undefined, tag: tagSel || undefined, retired: retiredSel, limit, offset });
807
812
  const resp = {
808
813
  items: items.map(hitToUiRecord),
809
814
  hasMore: offset + items.length < total,
@@ -292,6 +292,8 @@ export declare class L1Store {
292
292
  /** Room 过滤(metadata.tags 含该 slug)。 */
293
293
  tag?: string;
294
294
  workspaceId?: string;
295
+ /** 退场筛查:三态(`undefined` 全部 / `false` 仅活跃 / `true` 仅已退场)。 */
296
+ retired?: boolean;
295
297
  limit: number;
296
298
  offset: number;
297
299
  }): {
@@ -385,6 +385,15 @@ export declare class MemoryDb {
385
385
  /** Room 过滤(metadata.tags 含该 slug)。 */
386
386
  tag?: string;
387
387
  workspaceId?: string;
388
+ /**
389
+ * 退场(软删)筛查:三态。
390
+ * - 省略(undefined):不过滤,活跃+已退场都列(快照导出/重建需要全量;面板「全部」同此);
391
+ * - `false`:只列**活跃**记录(`valid_to` 为空);
392
+ * - `true`:只列**已退场**记录(`valid_to` 非空)——与 `listRetiredL1` 同口径。
393
+ * 面板默认不过滤而是靠徽标区分(「退场了」与「根本没这条」必须能分辨),
394
+ * 筛查只是把混排的两种态**分开看**,不改变"浏览路径不隐藏退场记录"的设计。
395
+ */
396
+ retired?: boolean;
388
397
  limit: number;
389
398
  offset: number;
390
399
  }): {
@@ -1486,6 +1486,14 @@ export class MemoryDb {
1486
1486
  where.push(`EXISTS (SELECT 1 FROM json_each(CASE WHEN json_valid(metadata_json) THEN metadata_json ELSE '{}' END, '$.tags') j WHERE j.value = ?)`);
1487
1487
  params.push(opts.tag);
1488
1488
  }
1489
+ // 退场筛查(三态,见 opts.retired 注释):与 listRetiredL1 同一判据,保证
1490
+ // 「仅退场」视图与「已退场」区看到同一批行。
1491
+ if (opts.retired === false) {
1492
+ where.push("COALESCE(valid_to, '') = ''");
1493
+ }
1494
+ else if (opts.retired === true) {
1495
+ where.push("COALESCE(valid_to, '') <> ''");
1496
+ }
1489
1497
  const whereSql = where.length > 0 ? ` WHERE ${where.join(' AND ')}` : '';
1490
1498
  const totalRow = this.db.prepare(`SELECT COUNT(*) AS n FROM l1_records${whereSql}`).get(...params);
1491
1499
  const rows = this.db
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.16.1-beta.0",
3
+ "version": "0.16.1-beta.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",