dsh-prime-memory 0.20.4 → 0.22.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
@@ -17,6 +17,13 @@
17
17
  - **建议状态(`longtask-hint-get`)以纯端点提供**:判定 = 上下文占用达 `contextThresholdPct` 或 todo 漂移达 `driftThreshold`(部署配置可调);配合 `longtask-compress-tail` 作手动压缩入口。
18
18
  - **撤下浮动球 UI(`client/src/longtask-fab.ts`;beta.1 起曾随包发布)**:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——2026-10-02 用户裁定撤下。撤下不违座位契约(settings.section 恰一个 + 输入栏 pill,body 单例不占座位),但全应用级 overlay 会与其他插件的悬浮 UI 抢占屏幕角落;长任务开关与手动压缩入口待长任务定版时落记忆设置分节。
19
19
  - **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
20
+ ## [0.21.0] — 2026-10-04
21
+
22
+ ### 新增
23
+
24
+ - **工具分组封印(记忆库面板 → 记忆 → 工具封印)**:5 组开关(room 管理 / graph 图谱 / ruminate 反刍 / conflict 冲突 / mutate 写删,覆盖 13 个工具)。关闭 = 该组工具**不注册**,从模型工具列表直接消失(不是执行期拒绝);翻转经 volatile 热更即时生效,无需重启。核心读(memory_search / memory_read_scene / memory_receipts / conversation_search)与槽位三件不受控。背景:多数分组工具(如 Room 治理、反刍)日常使用频率极低,常驻工具列表徒占注意力并诱导模型误调用。
25
+ - **memory_slot_write 的 mem_ 引用存在性校验**:refs 中 `mem_*` 形态的 L1 record_id 写入前经 `getByIds` 存在性校验,幻觉/拼错的 id 直接拒绝并提示走 memory_search——保证"search 激活的记忆"进入槽位引用的必然是真实记录(brief 侧的名称简述解析因此有可靠输入)。文件路径/URL 类 refs 不校验。
26
+
20
27
  ## [0.20.4] — 2026-10-04(本地构建,未发布)
21
28
 
22
29
  ### 修复
package/dist/client.js CHANGED
@@ -229,7 +229,8 @@ var __defProp = Object.defineProperty;
229
229
  " --dsh-mem-bg-card: var(--dsw-alias-bg-layer-2, #ffffff);",
230
230
  " --dsh-mem-bg-inset: var(--dsw-alias-bg-overlay, #e9ecf2);",
231
231
  " --dsh-mem-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(38,49,72,0.06));",
232
- " --dsh-mem-bg-pop: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3, #ffffff));",
232
+ " --dsh-mem-bg-pop: var(--dsw-alias-bg-layer-3, #ffffff);",
233
+ " --dsh-mem-bg-pop-base: var(--dsw-alias-bg-primary, #ffffff);",
233
234
  " --dsh-mem-border: var(--dsw-alias-border-l2, rgba(0,0,0,0.10));",
234
235
  " --dsh-mem-border-strong: var(--dsw-alias-border-l3, rgba(0,0,0,0.12));",
235
236
  " --dsh-mem-border-pop: var(--dsw-alias-border-inverted, rgba(0,0,0,0));",
@@ -272,7 +273,8 @@ var __defProp = Object.defineProperty;
272
273
  " --dsh-mem-bg-card: var(--dsw-alias-bg-layer-2, #2c2c2e);",
273
274
  " --dsh-mem-bg-inset: var(--dsw-alias-bg-layer-1, #232324);",
274
275
  " --dsh-mem-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08));",
275
- " --dsh-mem-bg-pop: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3, #353638));",
276
+ " --dsh-mem-bg-pop: var(--dsw-alias-bg-layer-3, #353638);",
277
+ " --dsh-mem-bg-pop-base: var(--dsw-alias-bg-primary, #1b1c1e);",
276
278
  " --dsh-mem-border: var(--dsw-alias-border-l2, rgba(255,255,255,0.12));",
277
279
  " --dsh-mem-border-strong: var(--dsw-alias-border-l3, rgba(255,255,255,0.16));",
278
280
  " --dsh-mem-border-pop: var(--dsw-alias-border-inverted, rgba(255,255,255,0.06));",
@@ -350,7 +352,8 @@ var __defProp = Object.defineProperty;
350
352
  " position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;",
351
353
  " min-width: 100%; width: max-content; max-width: 340px; max-height: 264px;",
352
354
  " overflow-y: auto; overscroll-behavior: contain; padding: 4px;",
353
- " background: var(--dsh-mem-bg-pop); border: 1px solid var(--dsh-mem-border-pop);",
355
+ " // 不透明实底:基色垫底+别名色整层叠印(别名即使带 alpha 也被垫死),2026-10-09 用户修复",
356
+ " background: linear-gradient(var(--dsh-mem-bg-pop), var(--dsh-mem-bg-pop)) , var(--dsh-mem-bg-pop-base); border: 1px solid var(--dsh-mem-border-pop);",
354
357
  " border-radius: 12px; box-shadow: var(--dsh-mem-shadow-pop);",
355
358
  "}",
356
359
  ".dsh-mem-pop-opt {",
@@ -434,7 +437,7 @@ var __defProp = Object.defineProperty;
434
437
  ".dsh-mem-popover {",
435
438
  " border-radius: 12px;",
436
439
  " border: 1px solid var(--dsh-mem-border-pop);",
437
- " background: var(--dsh-mem-bg-pop);",
440
+ " background: linear-gradient(var(--dsh-mem-bg-pop), var(--dsh-mem-bg-pop)) , var(--dsh-mem-bg-pop-base);",
438
441
  " box-shadow: var(--dsh-mem-shadow-pop);",
439
442
  " color: var(--dsh-mem-text-1);",
440
443
  "}",
@@ -3118,6 +3121,11 @@ var __defProp = Object.defineProperty;
3118
3121
  }
3119
3122
  const mutate = settingsData && settingsData.settings ? !!settingsData.settings.memoryMutate : false;
3120
3123
  const cfEnabled = settingsData && settingsData.settings ? !!settingsData.settings.conflictFreeze : false;
3124
+ const toolRoom = settingsData && settingsData.settings ? settingsData.settings.toolRoom !== false : true;
3125
+ const toolGraph = settingsData && settingsData.settings ? settingsData.settings.toolGraph !== false : true;
3126
+ const toolRuminate = settingsData && settingsData.settings ? settingsData.settings.toolRuminate !== false : true;
3127
+ const toolConflict = settingsData && settingsData.settings ? settingsData.settings.toolConflict !== false : true;
3128
+ const toolMutate = settingsData && settingsData.settings ? settingsData.settings.toolMutate !== false : true;
3121
3129
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3122
3130
  settingsData && settingsData.supported === false ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: S.hint, children: "settings 服务不可用,记忆模式开关未启用(记忆保持全开)。" }) : settingsData ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: S.switchPanel, children: [
3123
3131
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.panelLabel, children: "记忆模式" }),
@@ -3180,6 +3188,62 @@ var __defProp = Object.defineProperty;
3180
3188
  }
3181
3189
  }
3182
3190
  ),
3191
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.panelLabel, children: "工具封印" }),
3192
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3193
+ SwitchRow,
3194
+ {
3195
+ label: "Room 管理工具",
3196
+ desc: toolRoom ? "已注册:memory_room_admin / memory_room_review(目录治理)" : "已封印:两个工具从模型工具列表移除",
3197
+ checked: toolRoom,
3198
+ onChange: (v) => {
3199
+ toggle("toolRoom", v);
3200
+ }
3201
+ }
3202
+ ),
3203
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3204
+ SwitchRow,
3205
+ {
3206
+ label: "图谱工具",
3207
+ desc: toolGraph ? "已注册:memory_search_graph / memory_expand_graph_node" : "已封印:图谱读工具从模型工具列表移除",
3208
+ checked: toolGraph,
3209
+ onChange: (v) => {
3210
+ toggle("toolGraph", v);
3211
+ }
3212
+ }
3213
+ ),
3214
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3215
+ SwitchRow,
3216
+ {
3217
+ label: "反刍工具",
3218
+ desc: toolRuminate ? "已注册:memory_ruminate / _cancel / _status" : "已封印:反刍三件从模型工具列表移除",
3219
+ checked: toolRuminate,
3220
+ onChange: (v) => {
3221
+ toggle("toolRuminate", v);
3222
+ }
3223
+ }
3224
+ ),
3225
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3226
+ SwitchRow,
3227
+ {
3228
+ label: "冲突工具",
3229
+ desc: toolConflict ? "已注册:memory_conflicts / _rejected / resolve_conflict" : "已封印:冲突三件从模型工具列表移除",
3230
+ checked: toolConflict,
3231
+ onChange: (v) => {
3232
+ toggle("toolConflict", v);
3233
+ }
3234
+ }
3235
+ ),
3236
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3237
+ SwitchRow,
3238
+ {
3239
+ label: "写删工具",
3240
+ desc: toolMutate ? "已注册:memory_add / memory_import / memory_delete(仍需高权限模式)" : "已封印:L1 写删工具从模型工具列表移除(槽位三件不受此开关影响)",
3241
+ checked: toolMutate,
3242
+ onChange: (v) => {
3243
+ toggle("toolMutate", v);
3244
+ }
3245
+ }
3246
+ ),
3183
3247
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.panelLabel, children: "人工冲突裁决" }),
3184
3248
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3185
3249
  SwitchRow,
package/dist/config.d.ts CHANGED
@@ -322,6 +322,11 @@ export declare function liveSettingsSchema(): Schema<NoInfer<Schemastery.ObjectS
322
322
  embedRemoteDimensions: Schema<number, number, "defined">;
323
323
  memoryMutate: Schema<boolean, boolean, "defined">;
324
324
  conflictFreeze: Schema<boolean, boolean, "defined">;
325
+ toolRoom: Schema<boolean, boolean, "defined">;
326
+ toolGraph: Schema<boolean, boolean, "defined">;
327
+ toolRuminate: Schema<boolean, boolean, "defined">;
328
+ toolConflict: Schema<boolean, boolean, "defined">;
329
+ toolMutate: Schema<boolean, boolean, "defined">;
325
330
  }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
326
331
  enabled: Schema<boolean, boolean, "defined">;
327
332
  capture: Schema<boolean, boolean, "defined">;
@@ -419,6 +424,11 @@ export declare function liveSettingsSchema(): Schema<NoInfer<Schemastery.ObjectS
419
424
  embedRemoteDimensions: Schema<number, number, "defined">;
420
425
  memoryMutate: Schema<boolean, boolean, "defined">;
421
426
  conflictFreeze: Schema<boolean, boolean, "defined">;
427
+ toolRoom: Schema<boolean, boolean, "defined">;
428
+ toolGraph: Schema<boolean, boolean, "defined">;
429
+ toolRuminate: Schema<boolean, boolean, "defined">;
430
+ toolConflict: Schema<boolean, boolean, "defined">;
431
+ toolMutate: Schema<boolean, boolean, "defined">;
422
432
  }>>>, "volatile">;
423
433
  export declare const memorySchema: Schema<Schemastery.ObjectS<NoInfer<{
424
434
  dataDir: Schema<string, string, "defined">;
@@ -826,6 +836,11 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<NoInfer<{
826
836
  embedRemoteDimensions: Schema<number, number, "defined">;
827
837
  memoryMutate: Schema<boolean, boolean, "defined">;
828
838
  conflictFreeze: Schema<boolean, boolean, "defined">;
839
+ toolRoom: Schema<boolean, boolean, "defined">;
840
+ toolGraph: Schema<boolean, boolean, "defined">;
841
+ toolRuminate: Schema<boolean, boolean, "defined">;
842
+ toolConflict: Schema<boolean, boolean, "defined">;
843
+ toolMutate: Schema<boolean, boolean, "defined">;
829
844
  }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
830
845
  enabled: Schema<boolean, boolean, "defined">;
831
846
  capture: Schema<boolean, boolean, "defined">;
@@ -923,6 +938,11 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<NoInfer<{
923
938
  embedRemoteDimensions: Schema<number, number, "defined">;
924
939
  memoryMutate: Schema<boolean, boolean, "defined">;
925
940
  conflictFreeze: Schema<boolean, boolean, "defined">;
941
+ toolRoom: Schema<boolean, boolean, "defined">;
942
+ toolGraph: Schema<boolean, boolean, "defined">;
943
+ toolRuminate: Schema<boolean, boolean, "defined">;
944
+ toolConflict: Schema<boolean, boolean, "defined">;
945
+ toolMutate: Schema<boolean, boolean, "defined">;
926
946
  }>>>, "volatile">;
927
947
  }>>, Schemastery.ObjectT<NoInfer<{
928
948
  dataDir: Schema<string, string, "defined">;
@@ -1330,6 +1350,11 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<NoInfer<{
1330
1350
  embedRemoteDimensions: Schema<number, number, "defined">;
1331
1351
  memoryMutate: Schema<boolean, boolean, "defined">;
1332
1352
  conflictFreeze: Schema<boolean, boolean, "defined">;
1353
+ toolRoom: Schema<boolean, boolean, "defined">;
1354
+ toolGraph: Schema<boolean, boolean, "defined">;
1355
+ toolRuminate: Schema<boolean, boolean, "defined">;
1356
+ toolConflict: Schema<boolean, boolean, "defined">;
1357
+ toolMutate: Schema<boolean, boolean, "defined">;
1333
1358
  }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
1334
1359
  enabled: Schema<boolean, boolean, "defined">;
1335
1360
  capture: Schema<boolean, boolean, "defined">;
@@ -1427,6 +1452,11 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<NoInfer<{
1427
1452
  embedRemoteDimensions: Schema<number, number, "defined">;
1428
1453
  memoryMutate: Schema<boolean, boolean, "defined">;
1429
1454
  conflictFreeze: Schema<boolean, boolean, "defined">;
1455
+ toolRoom: Schema<boolean, boolean, "defined">;
1456
+ toolGraph: Schema<boolean, boolean, "defined">;
1457
+ toolRuminate: Schema<boolean, boolean, "defined">;
1458
+ toolConflict: Schema<boolean, boolean, "defined">;
1459
+ toolMutate: Schema<boolean, boolean, "defined">;
1430
1460
  }>>>, "volatile">;
1431
1461
  }>>, "plain">;
1432
1462
  export declare function resolveDataDir(cfg: MemoryConfig): string;
package/dist/config.js CHANGED
@@ -69,6 +69,13 @@ export function liveSettingsSchema() {
69
69
  memoryMutate: Schema.boolean().default(false),
70
70
  // §C 人工冲突裁决总开关:默认 false(冻结消耗注意力,不可默认全开)
71
71
  conflictFreeze: Schema.boolean().default(false),
72
+ // 工具分组封印(v0.21.0):false = 该组工具不注册,从模型工具列表消失。
73
+ // 核心读(memory_search/read_scene/receipts/conversation_search)与槽位三件不受控。
74
+ toolRoom: Schema.boolean().default(true),
75
+ toolGraph: Schema.boolean().default(true),
76
+ toolRuminate: Schema.boolean().default(true),
77
+ toolConflict: Schema.boolean().default(true),
78
+ toolMutate: Schema.boolean().default(true),
72
79
  }).volatile();
73
80
  }
74
81
  export const memorySchema = Schema.object({
@@ -47,6 +47,12 @@ export type LayerRouteKey = 'l1' | 'l2' | 'l3';
47
47
  export interface MemoryLiveSettings {
48
48
  /** 总开关:关 = 捕获/蒸馏/召回注入全停(数据保留) */
49
49
  enabled: boolean;
50
+ /** 工具分组封印:false = 该组工具不注册,从模型工具列表消失。核心读(memory_search/read_scene/receipts/conversation_search)与槽位三件不受控 */
51
+ toolRoom: boolean;
52
+ toolGraph: boolean;
53
+ toolRuminate: boolean;
54
+ toolConflict: boolean;
55
+ toolMutate: boolean;
50
56
  /** L0 捕获(原始对话落盘) */
51
57
  capture: boolean;
52
58
  /** L1 抽取 + L2/L3 蒸馏 */
@@ -209,7 +209,7 @@ usage) {
209
209
  let ackDegradeWarned = false;
210
210
  // §E 压缩感知(memorax-absorb):宿主 `compaction/end`(无 error)→ 该会话下一轮
211
211
  // 做一次"定向增强召回"——跳过去重压制、画像立即重注入、占用账本归零;消费即清。
212
- // 会话级回退路径保留(下方 session-start source='compact' 全量重置),两者幂等。
212
+ // 会话级回退路径保留(下方 agent/created source='compact' 全量重置),两者幂等。
213
213
  // 宿主无压缩计数器(compactionId 为 per-transaction UUID),故不做 generation 对账。
214
214
  const postCompaction = new PostCompactionTracker();
215
215
  ctx.on('session/event', (session, event) => {
package/dist/index.d.ts CHANGED
@@ -421,6 +421,11 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
421
421
  embedRemoteDimensions: import("@deepseek-ai/schemastery").default<number, number, "defined">;
422
422
  memoryMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
423
423
  conflictFreeze: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
424
+ toolRoom: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
425
+ toolGraph: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
426
+ toolRuminate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
427
+ toolConflict: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
428
+ toolMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
424
429
  }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
425
430
  enabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
426
431
  capture: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
@@ -518,6 +523,11 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
518
523
  embedRemoteDimensions: import("@deepseek-ai/schemastery").default<number, number, "defined">;
519
524
  memoryMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
520
525
  conflictFreeze: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
526
+ toolRoom: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
527
+ toolGraph: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
528
+ toolRuminate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
529
+ toolConflict: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
530
+ toolMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
521
531
  }>>>, "volatile">;
522
532
  }>>, Schemastery.ObjectT<NoInfer<{
523
533
  dataDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
@@ -925,6 +935,11 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
925
935
  embedRemoteDimensions: import("@deepseek-ai/schemastery").default<number, number, "defined">;
926
936
  memoryMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
927
937
  conflictFreeze: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
938
+ toolRoom: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
939
+ toolGraph: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
940
+ toolRuminate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
941
+ toolConflict: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
942
+ toolMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
928
943
  }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
929
944
  enabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
930
945
  capture: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
@@ -1022,6 +1037,11 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
1022
1037
  embedRemoteDimensions: import("@deepseek-ai/schemastery").default<number, number, "defined">;
1023
1038
  memoryMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1024
1039
  conflictFreeze: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1040
+ toolRoom: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1041
+ toolGraph: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1042
+ toolRuminate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1043
+ toolConflict: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1044
+ toolMutate: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
1025
1045
  }>>>, "volatile">;
1026
1046
  }>>, "plain">;
1027
1047
  export declare function apply(ctx: Context, config: MemoryConfig): Promise<void>;
@@ -156,13 +156,20 @@ export function registerSlotsProjection(ctx, store, l1) {
156
156
  .sessionProjections;
157
157
  if (registry === undefined || typeof registry.register !== 'function')
158
158
  return;
159
- registry.register({
159
+ // 双代平面(TL beta.29 同款判别式):0.1.0-rc.8 的 registry 是唯一的平面契约
160
+ // (def.schema.parse(def.view(state))),0.1.1+ 消费 wire:{viewSchema,view}。
161
+ // wire 形状镜像成平面键挂上,两代 registry 各取所需;一个缺 schema 的注册
162
+ // 单元会毒化旧 registry 的全部冷会话历史加载。
163
+ const definition = {
160
164
  key: MEMORY_SLOTS_KEY,
161
165
  stateSchema,
162
166
  init: () => buildState(store, l1),
163
167
  apply: (state, event) => applySlotsEvent(state, event, store, l1),
164
168
  wire: { viewSchema, view },
169
+ schema: viewSchema,
170
+ view,
165
171
  stateVersion: 0,
166
- });
172
+ };
173
+ registry.register(definition);
167
174
  });
168
175
  }
package/dist/settings.js CHANGED
@@ -1,4 +1,4 @@
1
- import { EFFORT_CHOICES } from './config.js';
1
+ import { EFFORT_CHOICES, liveSettingsSchema } from './config.js';
2
2
  /** 运行时路由链上限(写入门与 UI 同限,防误粘贴巨数组撑爆 settings 存储)。 */
3
3
  export const DISTILL_CHAIN_MAX = 8;
4
4
  /**
@@ -80,6 +80,11 @@ const ALWAYS_ON = {
80
80
  embedRemoteDimensions: 0,
81
81
  memoryMutate: false,
82
82
  conflictFreeze: false,
83
+ toolRoom: true,
84
+ toolGraph: true,
85
+ toolRuminate: true,
86
+ toolConflict: true,
87
+ toolMutate: true,
83
88
  };
84
89
  /** 自带自定义设置页(settings.section 顶层「记忆」分节,client 半挂载):
85
90
  * 关掉宿主按 volatile 字段自动生成的表单页,避免同一个插件出现两份设置入口。
@@ -115,23 +120,115 @@ export function registerLiveSettings(ctx, config, logger) {
115
120
  ? `,蒸馏模型=${current.distillProvider}/${current.distillModel}`
116
121
  : '') + budgetNote + inputNote);
117
122
  });
118
- // 写面:settings 服务(宿主服务,可能晚于本插件就绪)——用 inject 惰性握手,
119
- // 服务替换时 cordis 会重跑回调换上新实例,无需手工判活。
123
+ // ── 读写面三代腰(纯特性检测,playbook §5:勿按版本号分支)──────────────
124
+ // 代 1(0.1.7+):settings 服务有 SettingsForms.update(ns,patch) —— 读走 Config
125
+ // live volatile 引用(read()),写走 forms.update(整节提交)。
126
+ // 代 2(0.1.2/0.1.5):settings.register 返回 SettingsScope(get/watch/update)
127
+ // —— 读写全走 scope(写活读死:0.1.2/0.1.5 的 scope.get() 快照可能恒默认,
128
+ // 磁盘才是真值,调用方以持久化成功为准)。
129
+ // 代 2'(register 缺失):installSection 只回 hooks —— 读走 setSource 注入的
130
+ // thunk,写显式拒绝。
131
+ // 代 3(0.1.0/0.1.1):settings 服务整体缺席 → 恒开降级,internal/service 上线
132
+ // 重试。参考件:compat/0.1.5 registerLiveSettings 的 tryAttach 全套。
120
133
  let forms;
121
134
  ctx.inject(['settings'], (sctx) => {
122
135
  forms = sctx.get('settings');
123
136
  });
137
+ let inner = {
138
+ supported: false,
139
+ get: () => ({ ...ALWAYS_ON, distillChain: [] }),
140
+ update: () => Promise.reject(new Error('settings 服务不可用,记忆开关无法写入')),
141
+ };
142
+ const wireScope = (scope) => {
143
+ return {
144
+ supported: true,
145
+ get: () => resolveSettings(scope.get()),
146
+ update: async (patch) => {
147
+ await scope.update(patch);
148
+ },
149
+ };
150
+ };
151
+ const tryAttachOld = () => {
152
+ const settings = ctx.get('settings');
153
+ if (!settings || typeof forms?.update === 'function')
154
+ return true; // 代 1 生效,老臂不必挂
155
+ if (typeof settings.register === 'function') {
156
+ try {
157
+ const scope = settings.register(MEMORY_ENTRY_ID, liveSettingsSchema(), { applies: 'live' });
158
+ inner = wireScope(scope);
159
+ logger.info('[memory] 记忆模式开关就绪(settings.register,命名空间 dsh-memory)');
160
+ return true;
161
+ }
162
+ catch (err) {
163
+ logger.warn(`[memory] 记忆模式开关注册失败(保持全开): ${err instanceof Error ? err.message : String(err)}`);
164
+ return true;
165
+ }
166
+ }
167
+ if (typeof settings.installSection === 'function') {
168
+ try {
169
+ let source = () => ({ ...ALWAYS_ON, distillChain: [] });
170
+ let notify;
171
+ const bridge = {
172
+ get: () => resolveSettings(source()),
173
+ watch: (callback) => {
174
+ notify = () => void callback(bridge.get());
175
+ return () => {
176
+ notify = undefined;
177
+ };
178
+ },
179
+ update: () => Promise.reject(new Error('installSection 桥接模式不支持运行时写入')),
180
+ };
181
+ settings.installSection(ctx, MEMORY_ENTRY_ID, liveSettingsSchema(), { ...ALWAYS_ON, distillChain: [] }, {
182
+ setSource: (current) => {
183
+ source = current;
184
+ },
185
+ onChange: () => notify?.(),
186
+ });
187
+ inner = wireScope(bridge);
188
+ logger.info('[memory] 记忆模式开关就绪(settings.installSection 桥接,运行时写入不可用)');
189
+ return true;
190
+ }
191
+ catch (err) {
192
+ logger.warn(`[memory] 记忆模式开关 installSection 桥接失败(保持全开): ${err instanceof Error ? err.message : String(err)}`);
193
+ return true;
194
+ }
195
+ }
196
+ logger.warn('[memory] settings 服务无可用的 register/installSection API,记忆模式开关降级为恒开');
197
+ return true;
198
+ };
199
+ if (!tryAttachOld()) {
200
+ logger.warn('[memory] settings 服务未就绪,记忆模式开关暂不可用(保持全开,等待服务上线)');
201
+ }
202
+ // 服务迁移自愈(代 2/2' 臂):下线 → 降级;换实例/上线 → 立即重试挂接(幂等)。
203
+ ctx.on('internal/service', (name) => {
204
+ if (name !== 'settings')
205
+ return;
206
+ inner = {
207
+ supported: false,
208
+ get: () => ({ ...ALWAYS_ON, distillChain: [] }),
209
+ update: () => Promise.reject(new Error('settings 服务不可用,记忆开关无法写入')),
210
+ };
211
+ tryAttachOld();
212
+ });
213
+ const modernWrite = () => {
214
+ const f = forms;
215
+ return !!(f && typeof f.update === 'function');
216
+ };
124
217
  return {
125
218
  supported: true,
126
- get: read,
219
+ get: () => (modernWrite() ? read() : inner.get()),
127
220
  update: async (patch) => {
128
- if (!forms)
129
- throw new Error('settings 服务不可用,记忆开关无法写入');
130
- // patch 合并语义与旧 scope.update 一致:只改传入键。合并在插件侧完成后
131
- // **整节提交**——宿主的表单 update 按 volatile 节整体写入 profile patch,
132
- // 部分对象会覆盖掉未提交字段。写失败(update 抛错)对调用方可观测。
133
- const next = { ...read(), ...patch };
134
- await forms.update(MEMORY_ENTRY_ID, { live: next });
221
+ if (modernWrite()) {
222
+ if (!forms)
223
+ throw new Error('settings 服务不可用,记忆开关无法写入');
224
+ // patch 合并语义与旧 scope.update 一致:只改传入键。合并在插件侧完成后
225
+ // **整节提交**——宿主的表单 update 按 volatile 节整体写入 profile patch,
226
+ // 部分对象会覆盖掉未提交字段。写失败(update 抛错)对调用方可观测。
227
+ const next = { ...read(), ...patch };
228
+ await forms.update(MEMORY_ENTRY_ID, { live: next });
229
+ return;
230
+ }
231
+ await inner.update(patch);
135
232
  },
136
233
  };
137
234
  }
@@ -204,5 +301,11 @@ function resolveSettings(value) {
204
301
  memoryMutate: v.memoryMutate === true,
205
302
  // §C 人工冲突裁决:严格 === true(默认关,冻结消耗注意力)
206
303
  conflictFreeze: v.conflictFreeze === true,
304
+ // 工具分组封印:严格 !== false(缺省/异常值 = 未封印,工具照常注册)
305
+ toolRoom: v.toolRoom !== false,
306
+ toolGraph: v.toolGraph !== false,
307
+ toolRuminate: v.toolRuminate !== false,
308
+ toolConflict: v.toolConflict !== false,
309
+ toolMutate: v.toolMutate !== false,
207
310
  };
208
311
  }
package/dist/stats.js CHANGED
@@ -804,6 +804,7 @@ export async function handleEndpoint(endpoint, payload, deps) {
804
804
  supported: live?.supported ?? false,
805
805
  settings: sanitizeSettings(s ?? {
806
806
  enabled: true, capture: true, distill: true, recall: true,
807
+ toolRoom: true, toolGraph: true, toolRuminate: true, toolConflict: true, toolMutate: true,
807
808
  reasoningEffort: '', distillProvider: '', distillModel: '', distillChain: [],
808
809
  distillBudgets: { extract: 0, dedup: 0, l2: 0, l3: 0, graph: 0 }, distillMaxInputChars: 0,
809
810
  distillLayerChains: { l1: [], l2: [], l3: [] },
@@ -849,7 +850,7 @@ export async function handleEndpoint(endpoint, payload, deps) {
849
850
  const patch = (payload ?? {});
850
851
  const clean = {};
851
852
  // 布尔开关组:memoryMutate(高权限写删门)与主开关同列;conflictFreeze(§C 人工冲突裁决)
852
- for (const key of ['enabled', 'capture', 'distill', 'recall', 'memoryMutate', 'conflictFreeze']) {
853
+ for (const key of ['enabled', 'capture', 'distill', 'recall', 'memoryMutate', 'conflictFreeze', 'toolRoom', 'toolGraph', 'toolRuminate', 'toolConflict', 'toolMutate']) {
853
854
  if (typeof patch[key] === 'boolean')
854
855
  clean[key] = patch[key];
855
856
  }
@@ -38,6 +38,49 @@ ruminate,
38
38
  roomRegistry) {
39
39
  if (!cfg.tools)
40
40
  return;
41
+ // ── 工具分组封印(v0.21.0):sealable 组的注册经 reg() 登记,按 live 开关动态
42
+ // 注册/反注册。封印 = 从模型工具列表消失(反注册),不是执行期拒绝;开关经
43
+ // 设置页 volatile 热更(loader/volatile-update)即时生效,无需重启。
44
+ // 核心读(memory_search / conversation_search / memory_read_scene /
45
+ // memory_receipts)与槽位三件(slots.ts)不参与封印。
46
+ const toolDisposers = new Map();
47
+ const toolMakers = new Map();
48
+ const toolSealed = (group) => {
49
+ const snap = live.get();
50
+ switch (group) {
51
+ case 'room': return snap.toolRoom === false;
52
+ case 'graph': return snap.toolGraph === false;
53
+ case 'ruminate': return snap.toolRuminate === false;
54
+ case 'conflict': return snap.toolConflict === false;
55
+ case 'mutate': return snap.toolMutate === false;
56
+ default: return false;
57
+ }
58
+ };
59
+ const reg = (group, make) => {
60
+ const makers = toolMakers.get(group) ?? [];
61
+ makers.push(make);
62
+ toolMakers.set(group, makers);
63
+ if (!toolSealed(group)) {
64
+ const disposers = toolDisposers.get(group) ?? [];
65
+ disposers.push(make());
66
+ toolDisposers.set(group, disposers);
67
+ }
68
+ };
69
+ const applyToolSeals = () => {
70
+ for (const [group, disposers] of [...toolDisposers]) {
71
+ if (!toolSealed(group))
72
+ continue;
73
+ disposers.forEach((dispose) => dispose());
74
+ toolDisposers.delete(group);
75
+ logger.warn(`[memory] 工具组已封印: ${group}`);
76
+ }
77
+ for (const [group, makers] of [...toolMakers]) {
78
+ if (toolSealed(group) || toolDisposers.has(group))
79
+ continue;
80
+ toolDisposers.set(group, makers.map((make) => make()));
81
+ logger.warn(`[memory] 工具组已解封: ${group}`);
82
+ }
83
+ };
41
84
  /**
42
85
  * 沿父链解析**有效档位归属会话**(§A 修复)。
43
86
  *
@@ -189,7 +232,7 @@ roomRegistry) {
189
232
  }));
190
233
  // ── memory_room_admin: Room 粒度目录治理(分类管理 beta.4;beta.5 起 merge/rename/retire 已注册) ──
191
234
  // list 免预算零风险;merge/rename/retire 走 dryRun 预览→实跑,执行前自动备份,完成入队场景重算。
192
- ctx.tools.register(defineTool({
235
+ reg('room', () => ctx.tools.register(defineTool({
193
236
  name: 'memory_room_admin',
194
237
  description: 'Room 分类管理(目录粒度)。action="list" 列出注册表条目与自生长 Room 计数;' +
195
238
  'action="register" 预注册一个 Room(高权限;两级制——大类上限 200,小类用「大类/小类」细分不占额度,如 dsh-plugin/merge;' +
@@ -333,7 +376,7 @@ roomRegistry) {
333
376
  }
334
377
  return { notice: `非法 action:${action}(允许 list/register/merge/rename/retire)。` };
335
378
  },
336
- }));
379
+ })));
337
380
  // ── conversation_search: L0 原始对话 ──
338
381
  ctx.tools.register(defineTool({
339
382
  name: 'conversation_search',
@@ -518,7 +561,7 @@ roomRegistry) {
518
561
  rewritten: { type: 'boolean', description: '是否已被后续条目改写/取代' },
519
562
  };
520
563
  // memory_add:显式"记得X"直接落库一条 L1 记忆(绕过抽取管线,需高权限)。
521
- ctx.tools.register(defineTool({
564
+ reg('mutate', () => ctx.tools.register(defineTool({
522
565
  name: 'memory_add',
523
566
  description: '直接写入一条结构化记忆(L1)。仅当用户显式要求"记住/记下 X"时用;需高权限模式开启。内容须是待记忆的事实/偏好/任务/规则,不应包含对话过程。',
524
567
  parameters: {
@@ -554,9 +597,9 @@ roomRegistry) {
554
597
  logger.info(`[memory] 高权限写入记忆(${record.type}${record.metadata?.hall ? '/' + String(record.metadata.hall) : ''},时间轴 ${record.persistence ?? '?'}):${record.content.slice(0, 120)}`);
555
598
  return { id: record.id };
556
599
  },
557
- }));
600
+ })));
558
601
  // memory_import:批量写入(工具刷写通道——外部记忆包导入/迁移,免逐条调用)。
559
- ctx.tools.register(defineTool({
602
+ reg('mutate', () => ctx.tools.register(defineTool({
560
603
  name: 'memory_import',
561
604
  description: '批量写入多条记忆(L1)。用于把已整理好的外部记忆(其他 AI 工具导出的记忆包)一次性导入;需高权限模式开启。去重与冲突判定由调用方先行完成(见 memport Skill):本工具只做结构校验与批内重复拦截,不替调用方裁决语义冲突。单次上限 200 条。',
562
605
  parameters: {
@@ -652,9 +695,9 @@ roomRegistry) {
652
695
  logger.info(`[memory] 批量导入 ${records.length} 条(跳过 ${skipped.length} 条,场景 ${scene})`);
653
696
  return { written: records.length, ids: records.map((r) => r.id), skipped };
654
697
  },
655
- }));
698
+ })));
656
699
  // memory_delete:显式"忘了 X"——按语义检索命中后删除(高权限门控)。
657
- ctx.tools.register(defineTool({
700
+ reg('mutate', () => ctx.tools.register(defineTool({
658
701
  name: 'memory_delete',
659
702
  description: '退场(软删)与查询相关的记忆(L1)。仅当用户显式要求"忘记/删除某条记忆"时用;需高权限模式开启。' +
660
703
  '默认只退场**最贴近的 1 条**;可用 limit 放大(上限 10)。' +
@@ -723,11 +766,11 @@ roomRegistry) {
723
766
  logger.info(`[memory] 高权限退场(软删)记忆 ${n} 条(${ids.join(',')})`);
724
767
  return { deleted: n, ids };
725
768
  },
726
- }));
769
+ })));
727
770
  // ── 图谱工具(读;受与 memory_search 同款的档位/注入拒读门 + 族过滤) ──
728
771
  const GRAPH_OFF_NOTICE = '图谱功能未启用:部署配置 graph.enabled 未开启,当前没有可用的知识图谱。';
729
772
  // memory_search_graph: 图谱节点检索(紧凑节点卡)
730
- ctx.tools.register(defineTool({
773
+ reg('graph', () => ctx.tools.register(defineTool({
731
774
  name: 'memory_search_graph',
732
775
  description: '搜索知识图谱(实体节点:人物/项目/组织/工具/地点)。返回实体的当前状态摘要与匹配说明,适用于查"某人/某项目现在什么状态"这类问题;需要完整属性与关系时再用 memory_expand_graph_node 展开。',
733
776
  parameters: {
@@ -785,9 +828,9 @@ roomRegistry) {
785
828
  })),
786
829
  };
787
830
  },
788
- }));
831
+ })));
789
832
  // memory_expand_graph_node: 展开单个节点(facts 全量含历史 + 关系边)
790
- ctx.tools.register(defineTool({
833
+ reg('graph', () => ctx.tools.register(defineTool({
791
834
  name: 'memory_expand_graph_node',
792
835
  description: '展开一个图谱节点的完整详情:全部属性(facts,含已被更新的历史值与生效区间)、关联关系边与来源记忆 id。先用 memory_search_graph 拿到节点 id。',
793
836
  parameters: {
@@ -843,12 +886,12 @@ roomRegistry) {
843
886
  lines.push('', `来源记忆: ${node.sourceRecordIds.join('、') || '(无)'}`);
844
887
  return { node: lines.join('\n') };
845
888
  },
846
- }));
889
+ })));
847
890
  // ── 反刍工具(按需触发 L1→L2→L3 消化,受蒸馏开关门控) ──
848
891
  const RUMINATE_OFF_NOTICE = '反刍功能未开放:请在记忆库面板开启「蒸馏」开关后使用。';
849
892
  const RUMINATE_UNAVAIL_NOTICE = '反刍未初始化:存储处于降级态,无法反刍。';
850
893
  const RUMINATE_RUNNING_NOTICE = '反刍已在进行中,请稍后再试。';
851
- ctx.tools.register(defineTool({
894
+ reg('ruminate', () => ctx.tools.register(defineTool({
852
895
  name: 'memory_ruminate',
853
896
  description: '触发记忆反刍:把未蒸馏的对话缓冲冲刷出来,跑一轮 L1 抽取 → L2 场景整合 → L3 画像更新。与重建不同,不清库不改 L0,仅消化攒而未蒸馏的切片;无缓冲时做轻量 L2/L3 刷新。',
854
897
  parameters: {},
@@ -894,9 +937,9 @@ roomRegistry) {
894
937
  return { notice: `反刍启动失败: ${msg}`, running: false, phase: 'failed', done: 0, total: 0, recordsBuilt: 0, startedAt: undefined };
895
938
  }
896
939
  },
897
- }));
940
+ })));
898
941
  // ── 取消反刍(按需取消正在进行的反刍) ──
899
- ctx.tools.register(defineTool({
942
+ reg('ruminate', () => ctx.tools.register(defineTool({
900
943
  name: 'memory_ruminate_cancel',
901
944
  description: '取消正在进行的记忆反刍。已蒸馏部分保留,pending 切片中未被消费的部分维持原状。',
902
945
  parameters: {},
@@ -928,12 +971,12 @@ roomRegistry) {
928
971
  return { notice: msg, phase: 'idle', running: false };
929
972
  }
930
973
  },
931
- }));
974
+ })));
932
975
  // ── 反刍状态查询(查看当前反刍进度) ──
933
976
  // ── memory_room_review:孤儿记忆的 Room 候选逐个复查(agent 长尾治理)──────
934
977
  // next/annotate 只读或预算内标注(无需门);confirm/skip 改写 metadata,
935
978
  // confirm 吃 live.memoryMutate 高权限门(与 memory_delete 同款)。
936
- ctx.tools.register(defineTool({
979
+ reg('room', () => ctx.tools.register(defineTool({
937
980
  name: 'memory_room_review',
938
981
  description: '孤儿记忆(无 Room 绑定)的逐个复查:action="next" 取下一条待复查记录(带系统预标记的候选 Room 与现有词表,agent 依据记录内容判定);' +
939
982
  'action="confirm" 按 id 把 1-3 个 Room slug 写入该记忆(高权限);action="skip" 跳过(不再进队列);' +
@@ -1010,8 +1053,8 @@ roomRegistry) {
1010
1053
  }
1011
1054
  return { notice: `非法 action:${action}(允许 next/confirm/skip/annotate)。剩余待复查 ${pendingReviewCount(stores.l1)} 条。` };
1012
1055
  },
1013
- }));
1014
- ctx.tools.register(defineTool({
1056
+ })));
1057
+ reg('ruminate', () => ctx.tools.register(defineTool({
1015
1058
  name: 'memory_ruminate_status',
1016
1059
  description: '查询当前记忆反刍的状态与进度(是否运行中、当前阶段、已完成/总会话数等)。\n\n注意:在 DSH Web GUI 的对话中无法直接调用此工具,因为模型可调用工具列表不包含 ruminate_status。如需查询进度,请在记忆库面板的蒸馏面板查看。',
1017
1060
  parameters: {},
@@ -1052,7 +1095,7 @@ roomRegistry) {
1052
1095
  error: result.error ?? undefined,
1053
1096
  };
1054
1097
  },
1055
- }));
1098
+ })));
1056
1099
  // ── memory_receipts: §B 决策凭证回溯(读;受与 memory_search 同款档位门) ──
1057
1100
  // 为什么给它一个模型可见的工具:凭证链的价值全在"事后能问"。若只有 RPC 端点,
1058
1101
  // 用户得自己去浏览器/curl 才能回溯,而真正会问「这条记忆怎么来的」的场合
@@ -1124,7 +1167,7 @@ roomRegistry) {
1124
1167
  // 但那个工具**一直不存在** —— 模型照着描述调用只会拿到"工具不存在"。
1125
1168
  // 裁决端点在、读端点与读工具两端都缺,队列于是成了只进不出的黑洞
1126
1169
  // (安全阀超时自动了结会成为唯一出路,那正是 §C 想避免的)。
1127
- ctx.tools.register(defineTool({
1170
+ reg('conflict', () => ctx.tools.register(defineTool({
1128
1171
  name: 'memory_conflicts',
1129
1172
  description: '列出**矛盾冻结**的待裁决对(§C)。冻结不自动裁决:新记忆照常入库,与它冲突的旧记忆作为**一对**停在队列里,双方内容都不被改写,直到人给出结论。返回每对的 pair_id、**双方正文**与 LLM 建议的胜负方(id 只是进入队列时的排序位,不代表结论)。看完用 memory_resolve_conflict 给出结论:winner / loser / both。',
1130
1173
  parameters: {
@@ -1181,12 +1224,12 @@ roomRegistry) {
1181
1224
  }
1182
1225
  return listConflictPairs({ l1: stores.l1, conflictFreezeEnabled: live.get().conflictFreeze === true }, { limit: typeof args.limit === 'number' ? args.limit : undefined });
1183
1226
  },
1184
- }));
1227
+ })));
1185
1228
  // ── memory_conflicts_rejected: §C 丢弃留痕的读出口 ──
1186
1229
  // LLM 输出不满足 pair 格式的冲突决策会被记入 `conflict_rejected` 表,
1187
1230
  // 但此前只有写入没有读取——人无法知道"哪些冲突被判定为不合法"。
1188
1231
  // 本工具补上读方向,与 `dsh-memory/conflicts-rejected` RPC 端点共用同一形状。
1189
- ctx.tools.register(defineTool({
1232
+ reg('conflict', () => ctx.tools.register(defineTool({
1190
1233
  name: 'memory_conflicts_rejected',
1191
1234
  description: '列出被**丢弃**的冲突决策(§C 丢弃留痕)。LLM 输出不满足 pair 格式(缺 winner/loser id、无法配对等)时,该决策会被记入丢弃表而非停到待裁决队列。返回每条的 reject_id、原始正文、丢弃原因与所属蒸馏批次。可用 limit / created_before 分页。',
1192
1235
  parameters: {
@@ -1249,12 +1292,12 @@ roomRegistry) {
1249
1292
  })),
1250
1293
  };
1251
1294
  },
1252
- }));
1295
+ })));
1253
1296
  // ── memory_resolve_conflict: §C 矛盾冻结的人工裁决出口 ──
1254
1297
  // 冻结把裁决权交还给人,那么**必须**有一个"人能把结论说回去"的出口——
1255
1298
  // 否则待裁决队列是个只进不出的黑洞,安全阀(task_24)会成为唯一出路,
1256
1299
  // 那等于把 opt-in 的冻结悄悄退回成"超时后机器自己判"。
1257
- ctx.tools.register(defineTool({
1300
+ reg('conflict', () => ctx.tools.register(defineTool({
1258
1301
  name: 'memory_resolve_conflict',
1259
1302
  description: '裁决一条**矛盾冻结**的待裁决对(§C)。冻结产生的冲突对停放在待裁决队列里,双方记忆都不被改写,直到你在这里给出结论:winner(判 LLM 建议的胜方为真,败方从检索中退场)、loser(判败方为真)、both(判定两者其实是各自独立的事实,都保留)、defer(看过但**暂不裁决**——它不关闭冲突,该对仍在待裁决队列里,会重置超时计时并累计复看次数)。需先开启 conflictFreeze 配置;待裁决对可用 memory_conflicts 查看。',
1260
1303
  parameters: {
@@ -1290,7 +1333,14 @@ roomRegistry) {
1290
1333
  return { ...empty, notice: '需要 pair_id:待裁决对没有"全部裁决"这种用法。' };
1291
1334
  return resolveConflictPair({ l1: stores.l1, conflictFreezeEnabled: live.get().conflictFreeze === true }, pairId, outcome);
1292
1335
  },
1293
- }));
1336
+ })));
1337
+ applyToolSeals();
1338
+ // 设置页翻开关 → volatile 热更 → 重新应用封印(反注册/重注册)。
1339
+ // 守卫:测试 fake ctx / 极老宿主没有事件面时静默跳过(封印仅失去热更,
1340
+ // 重启后仍生效)。
1341
+ if (typeof ctx.on === 'function') {
1342
+ ctx.on('loader/volatile-update', () => applyToolSeals());
1343
+ }
1294
1344
  logger.info('[memory] 工具已注册: memory_search / conversation_search / memory_read_scene / memory_receipts / memory_search_graph / memory_expand_graph_node,及高权限 memory_add/memory_import/memory_delete / memory_resolve_conflict / memory_ruminate / memory_ruminate_cancel / memory_ruminate_status');
1295
1345
  }
1296
1346
  /** 凭证回溯的人类可读渲染(含"还有多少条没显示")。 */
@@ -101,7 +101,7 @@ l1) {
101
101
  // ── memory_slot_write:写入新槽位(写,memoryMutate 门控) ──
102
102
  ctx.tools.register(defineTool({
103
103
  name: 'memory_slot_write',
104
- description: '写入一个激活槽位(active slot)。适合把跨会话需要持续生效的规则、待办、锚点或外部指针固化下来;pinned=true 的 open 槽位会常驻注入每轮对话上下文。需高权限模式开启。',
104
+ description: '写入一个激活槽位(active slot)。适合把跨会话需要持续生效的规则、待办、锚点或外部指针固化下来;pinned=true 的 open 槽位会常驻注入每轮对话上下文。需高权限模式开启。refs 中的 L1 record_id 必须真实存在(用 memory_search 返回的 id),否则写入被拒。',
105
105
  parameters: {
106
106
  title: { type: 'string', required: true, description: '槽位标题(≤60 字,一句话概括)' },
107
107
  kind: { type: 'string', description: '槽位类型:rule(规则)/todo(待办)/anchor(锚点)/pointer(指针,正文可空)' },
@@ -143,6 +143,20 @@ l1) {
143
143
  status: typeof args.status === 'string' ? args.status : undefined,
144
144
  origin: 'user',
145
145
  };
146
+ // mem_ 引用必须真实存在(v0.21.0):保证"search 激活的记忆"进入引用的是
147
+ // 真实 record_id——模型幻觉/拼错的 id 直接拒绝并给出修正路径,不让坏引用
148
+ // 静默进槽位(否则 brief 侧解析不出名称简述)。文件路径/URL 类 refs 不校验。
149
+ const memRefs = (input.refs ?? []).filter((r) => r.startsWith('mem_'));
150
+ if (memRefs.length > 0 && l1) {
151
+ const found = new Set(l1.getByIds([...memRefs]).map((r) => r.id));
152
+ const missing = memRefs.filter((id) => !found.has(id));
153
+ if (missing.length > 0) {
154
+ return {
155
+ notice: `写入被拒:refs 中的 L1 record_id 不存在(${missing.join(', ')})。` +
156
+ `请先用 memory_search 搜索,使用其返回的 id;文件路径/URL 类 refs 无需此校验。`,
157
+ };
158
+ }
159
+ }
146
160
  try {
147
161
  const slot = await slots.upsert(input);
148
162
  logger.info(`[memory] 写入激活槽位(${slot.kind},pinned=${slot.pinned}):${slot.title.slice(0, 60)}`);
package/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dsh-prime-memory",
3
3
  "name": "dsh-prime-memory",
4
- "version": "0.20.0-beta.6-dsh0.2.0.1",
4
+ "version": "0.22.1",
5
5
  "description": {
6
6
  "en": "Layered distilled memory plugin (L0 capture → L1 atomic memory → L2 scenes → L3 persona) with automatic recall injection before each model step.",
7
7
  "zh": "L0~L3 分层蒸馏记忆插件:自动捕获对话、抽取原子记忆、整合场景块、蒸馏核心画像,并在模型步骤前自动召回注入。"
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "license": "MIT",
15
15
  "engines": {
16
- "dsh": ">=0.2.0-rc.1 <0.2.1-0"
16
+ "dsh": ">=0.1.0-rc.2"
17
17
  },
18
18
  "components": {
19
19
  "host": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.20.4",
3
+ "version": "0.22.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
@@ -24,8 +24,7 @@
24
24
  },
25
25
  "client": {
26
26
  "inject": [
27
- "@deepseek-ai/dsh-client-ui-primitives",
28
- "@deepseek-ai/dsh-client-connection"
27
+ "@deepseek-ai/dsh-client-ui-primitives"
29
28
  ],
30
29
  "platform": "web"
31
30
  }
@@ -57,11 +56,12 @@
57
56
  "build:smoke": "tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution nodenext --target es2022 --strict --skipLibCheck --esModuleInterop",
58
57
  "lint": "eslint src client/src scripts tests",
59
58
  "test": "vitest run",
60
- "wing-migrate": "npm run build && node scripts/wing-migrate.mjs"
59
+ "wing-migrate": "npm run build && node scripts/wing-migrate.mjs",
60
+ "prepack": "npm run build"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=22.16.0",
64
- "dsh": ">=0.2.0-rc.1 <0.2.1-0"
64
+ "dsh": ">=0.1.0-rc.2"
65
65
  },
66
66
  "keywords": [
67
67
  "dsh",
@@ -83,15 +83,15 @@
83
83
  },
84
84
  "peerDependencies": {
85
85
  "@deepseek-ai/cordis": "^4.0.1",
86
- "@deepseek-ai/dsh-agent": ">=0.2.0-rc.1 <0.2.1-0",
87
- "@deepseek-ai/dsh-client-connection": ">=0.2.0-rc.1 <0.2.1-0",
88
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.2.0-rc.1 <0.2.1-0",
89
- "@deepseek-ai/dsh-home-paths": ">=0.2.0-rc.1 <0.2.1-0",
90
- "@deepseek-ai/dsh-llm": ">=0.2.0-rc.1 <0.2.1-0",
91
- "@deepseek-ai/dsh-session": ">=0.2.0-rc.1 <0.2.1-0",
92
- "@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1-0",
93
- "@deepseek-ai/dsh-system-prompt": ">=0.2.0-rc.1 <0.2.1-0",
94
- "@deepseek-ai/dsh-tools": ">=0.2.0-rc.1 <0.2.1-0"
86
+ "@deepseek-ai/dsh-agent": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-client-connection": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
89
+ "@deepseek-ai/dsh-home-paths": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
90
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
91
+ "@deepseek-ai/dsh-session": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
92
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
93
+ "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
94
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2"
95
95
  },
96
96
  "peerDependenciesMeta": {
97
97
  "@deepseek-ai/dsh-agent": {