@max-null/dsh-memory 0.17.0 → 0.18.0

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/README.md CHANGED
@@ -116,6 +116,8 @@ md 文件是唯一事实源(`~/.dsh/prompt-library/*.md` 为 global;`<worksp
116
116
  ```
117
117
  模型 memory_save → status: approved,立即生效;命中密钥/凭据规则则隔离(不进检索也不进注入)
118
118
  给 injected 则同时钉住常驻(0.8.0),不受自动规则影响
119
+ 钉成常驻时返回 `resident`(total / injected / omitted / budget)——
120
+ 钉的那一刻就知道挤掉了几条,不必等下一轮的出局诊断行
119
121
  memory_search → 关键词/语义召回(只搜记忆;模板走 prompt_search 通道,不会混进候选池)
120
122
  默认最多返回 20 条,`limit: 0` 取消上限;条数等于 limit 时可能还有更多
121
123
  命中记账只记 BM25 前 5 名——`hitCount` 是"排前"的信号,不是"被检索到"的面;
package/dist/index.js CHANGED
@@ -99,6 +99,32 @@ const HIT_SCHEMA = {
99
99
  score: { type: 'number', required: true },
100
100
  },
101
101
  };
102
+ /**
103
+ * `memory_save` / `memory_update` 的输出:记录本体 +(操作后仍是常驻时)预算占用快照。
104
+ *
105
+ * 单列一份而不往 {@link RECORD_SCHEMA} 里加:`memory_list` 一次返回全库,每条都带
106
+ * 一份占用快照既冗余,也与「这条记录本身」无关。预算快照只在**钉入/仍常驻**时出现,
107
+ * 因为那正是「挤掉别人」这个决定发生的那一刻——钉完再等下一轮的出局诊断行,人已经
108
+ * 想不起来是哪一次钉入造成的。
109
+ */
110
+ const SAVED_RECORD_SCHEMA = {
111
+ type: 'object',
112
+ additionalProperties: false,
113
+ properties: {
114
+ ...RECORD_SCHEMA.properties,
115
+ resident: {
116
+ type: 'object',
117
+ additionalProperties: false,
118
+ description: '钉入之后的常驻注入占用(仅当该记录为常驻时出现):total 候选条数、injected 本轮实际进场、omitted 被预算挤出、budget 字符预算。',
119
+ properties: {
120
+ total: { type: 'number', required: true },
121
+ injected: { type: 'number', required: true },
122
+ omitted: { type: 'number', required: true },
123
+ budget: { type: 'number' },
124
+ },
125
+ },
126
+ },
127
+ };
102
128
  /**
103
129
  * `memory_search` 的默认返回上限(0 = 不限)。宽查询会命中大半个库(本机 279 条库实测
104
130
  * 命中面 35%–84%),返回体随库线性增长;20 条仍覆盖"有答案价值"的部分,20 名之后
@@ -284,6 +310,21 @@ const GUIDANCE = 'Use memory tools for cross-session preferences, habits, and pr
284
310
  + 'When the library grows, call memory_sweep for a read-only health check (stale records with reasons, '
285
311
  + 'long-unused ones, candidates that keep being retrieved without being pinned, and budget usage). '
286
312
  + '库变大之后用 memory_sweep 做只读体检——失效记录(带原因)、久未命中、被反复检索却未常驻的候选、以及预算占用。';
313
+ /**
314
+ * 钉入常驻后的预算占用快照(`memory_save` / `memory_update` 输出用)。刻意复用注入侧的
315
+ * `renderInjection`:两处各算一套的话,「工具里显示的占用」与「下一轮实际注入」会给出
316
+ * 不同的数字,而人恰恰是拿前者的数字去判断要不要钉的。
317
+ */
318
+ function residentUsage(memory, projectCwd, budget, summaryChars) {
319
+ const records = memory.recallRecords(projectCwd);
320
+ const rendered = renderInjection(records, budget, summaryChars);
321
+ return {
322
+ total: records.length,
323
+ injected: rendered.lines.length,
324
+ omitted: rendered.omitted,
325
+ ...rendered.budget === null ? {} : { budget: rendered.budget },
326
+ };
327
+ }
287
328
  // 0.5.1:`memory:recall` provider 可经 AssembleContext.agent 拿到当前会话
288
329
  // 的 header.cwd——注入 = global + 当前会话工作区的 approved+injected
289
330
  // (0.3.4 起工作区记忆就按会话 cwd 路由;provider 是同步回调,只读已
@@ -435,10 +476,11 @@ export async function apply(ctx, config) {
435
476
  },
436
477
  },
437
478
  output: {
438
- schema: RECORD_SCHEMA,
479
+ schema: SAVED_RECORD_SCHEMA,
439
480
  render: (_args, value) => renderJson(value),
440
481
  },
441
482
  execute(args, exec) {
483
+ const cwd = execProjectCwd(exec);
442
484
  return memory.remember({
443
485
  content: args.content,
444
486
  ...args.namespace === undefined ? {} : { namespace: args.namespace },
@@ -448,7 +490,14 @@ export async function apply(ctx, config) {
448
490
  ...args.anchor === undefined
449
491
  ? {}
450
492
  : { anchor: args.anchor },
451
- }, execProjectCwd(exec)).then(recordValue);
493
+ }, cwd).then(record => {
494
+ const value = recordValue(record);
495
+ // 钉常驻的那一刻就把「挤掉了几条」交给人看:等到下一轮的出局诊断行,
496
+ // 人已经想不起来是哪一次钉入造成的。
497
+ return record.injected
498
+ ? { ...value, resident: residentUsage(memory, cwd, injectionBudget, summaryChars) }
499
+ : value;
500
+ });
452
501
  },
453
502
  presentCall: args => present('Save memory', 'other', args.content),
454
503
  }));
@@ -704,18 +753,25 @@ export async function apply(ctx, config) {
704
753
  },
705
754
  },
706
755
  output: {
707
- schema: RECORD_SCHEMA,
756
+ schema: SAVED_RECORD_SCHEMA,
708
757
  render: (_args, value) => renderJson(value),
709
758
  },
710
759
  execute(args, exec) {
711
760
  const cwd = execProjectCwd(exec);
712
761
  const id = args.id;
762
+ const settle = (record) => {
763
+ const value = recordValue(record);
764
+ // 改内容也会改变注入占用的字符数:只要这条仍是常驻,就把占用一起交回去。
765
+ return record.injected
766
+ ? { ...value, resident: residentUsage(memory, cwd, injectionBudget, summaryChars) }
767
+ : value;
768
+ };
713
769
  // 三种动作互斥,按 retract > supersedes > update 的次序取第一个(与工具描述一致):
714
770
  // 前两个是「标状态」,第三个才是「改内容」。同时给多个时以更重的动作为准。
715
771
  if (args.retract !== undefined)
716
- return memory.retract(id, args.retract, cwd).then(recordValue);
772
+ return memory.retract(id, args.retract, cwd).then(settle);
717
773
  if (args.supersedes !== undefined)
718
- return memory.supersede(id, args.supersedes, cwd).then(recordValue);
774
+ return memory.supersede(id, args.supersedes, cwd).then(settle);
719
775
  return memory.update(id, {
720
776
  ...args.content === undefined ? {} : { content: args.content },
721
777
  ...args.keywords === undefined ? {} : { keywords: args.keywords },
@@ -724,7 +780,7 @@ export async function apply(ctx, config) {
724
780
  ...args.anchor === undefined
725
781
  ? {}
726
782
  : { anchor: args.anchor },
727
- }, cwd).then(recordValue);
783
+ }, cwd).then(settle);
728
784
  },
729
785
  presentCall: args => present('Update memory', 'other', args.id),
730
786
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-memory",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, writes take effect immediately, signal-driven injection, no embeddings",
5
5
  "license": "MIT",
6
6
  "publishConfig": {