dsh-fish-memory 1.5.6 → 1.5.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.
Files changed (2) hide show
  1. package/lib/index.js +10 -48
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -157,7 +157,6 @@ const DEFAULT_POLICY = {
157
157
  rarityK: 0.8, // ⭐ 1.4.0:1 → 0.8。稀有度饱和点:加起来到多少算「一半相关」(越小越容易给)
158
158
  queryGate: 0.8, // 问句信息量门槛:这句话本身太没内容就不翻记忆。0 = 关掉这道门
159
159
  // ⭐ 2026-09-21 中午加的(C+D:两条队)
160
- factsMax: 2, // 长期事实每轮固定带几条(常驻层,不占 maxItems 的位置)。0 = 不带长期事实
161
160
  // ⭐ 2026-09-21 深夜(她的决定):默认从 1 调到 **0.5** ✓
162
161
  // 她的直觉:「我知道时间才是劣势」——有日期的那 188 条,反倒排不到没日期的前面去 ✗
163
162
  // 实测:门槛卡 C 之后这个旋钮**只影响排序**,扫 1/0.75/0.5/0.25/0「该给到」全是 11/13 一动不动 ✓
@@ -180,7 +179,6 @@ const POLICY_SPEC = [
180
179
  { key: 'dailyQuota', group: 'gate', label: '每天最多记几条', tip: '她自己一天最多记几条', min: 1, max: 30, step: 1 },
181
180
  { key: 'rarityK', group: 'gate', label: '稀有度饱和点', tip: '命中词的稀有度(0~1)加起来到多少算「一半相关」。越小越容易给', min: 0.3, max: 6, step: 0.1 },
182
181
  { key: 'queryGate', group: 'gate', label: '问句信息量门槛', tip: '这句话本身太没内容(像「在吗」)就不翻记忆。0 = 关掉这道门', min: 0, max: 2, step: 0.05 },
183
- { key: 'factsMax', group: 'gate', label: '长期事实每轮带几条', tip: '要记一辈子的事(画像、喜好、约定)每轮都带上,不看这句话问了什么,也不占「最多注入几条」的位置。0 = 不带长期事实', min: 0, max: 6, step: 1 },
184
182
  { key: 'unknownActivation', group: 'gate', label: '没写时间的算多鲜活', tip: '没写时间戳的记忆(含长期事实)鲜活度按几成算。1 = 当成刚发生的(默认)。调小只让它们排在有时间戳的后面,不影响谁进得来', min: 0, max: 1, step: 0.05 },
185
183
  ]
186
184
 
@@ -3338,32 +3336,6 @@ function 按键收敛(data, 被谁) {
3338
3336
  // (2026-09-23 尺子换成按词比,这个数跟着重量过,0.80 仍然合适)
3339
3337
  const 召回重复门槛 = 0.8
3340
3338
 
3341
- // 常驻层:长期事实每轮固定带 factsMax 条(按重要度、再按时间)。scored 里有的沿用它的分数行。
3342
- // 抽成函数是因为问句门挡下的那条早退路径也要用(见 recall 里的说明)。
3343
- function 选常驻事实(memories, scored, policy) {
3344
- const 事实上限 =
3345
- typeof policy.factsMax === 'number' && policy.factsMax >= 0 ? Math.floor(policy.factsMax) : 2
3346
- const 常驻事实 = []
3347
- if (事实上限 > 0) {
3348
- const 候选 = memories
3349
- .filter((m) => m && m.category === 'stable_profile')
3350
- .slice()
3351
- .sort(
3352
- (a, b) =>
3353
- (Number(b.importance) || 0) - (Number(a.importance) || 0) ||
3354
- (Date.parse(b.createdAt || b.at || 0) || 0) - (Date.parse(a.createdAt || a.at || 0) || 0),
3355
- )
3356
- const 行表 = new Map()
3357
- for (const row of scored) if (row && row.m) 行表.set(String(row.m.id), row)
3358
- for (const m of 候选.slice(0, 事实上限)) {
3359
- const row = 行表.get(String(m.id)) || { m, s: { P: 1, C: 1, timeHit: 1 } }
3360
- row.omission = null // 常驻的不算「被筛掉」
3361
- row.常驻 = true
3362
- 常驻事实.push(row)
3363
- }
3364
- }
3365
- return 常驻事实
3366
- }
3367
3339
 
3368
3340
  // 按「注入字数上限」收:第一条总能进,后面的超了就这趟先不进
3369
3341
  function 按字数收(rows, policy) {
@@ -3417,11 +3389,8 @@ function recall(data, query, now, sid) {
3417
3389
  const v = 问句信息量(sid, query, df)
3418
3390
  if (v < 门槛) {
3419
3391
  // 这道门挡下来的时候**不做 851 次打分**,省一趟;理由照样记进 trace 给她看 ✓
3420
- // ⚠ 但**常驻层照带**:常驻的意义就是不看这句话问了什么(issue #14)。
3421
- // 以前这里直接返回空 —— 「在吗」「我是谁」这种短句一被挡,长期事实也跟着没了 ✗
3422
- const 常驻 = 选常驻事实(memories, [], policy)
3423
- const 带上 = 按字数收(常驻, policy)
3424
- return { selected: 带上.selected, scored: 常驻, 字数: 带上.字数, gate: { 信息量: Number(v.toFixed(2)), 门槛, 挡了: true } }
3392
+ // 门挡住了就什么都不带(长期事实那层已经删掉了)
3393
+ return { selected: [], scored: [], 字数: 0, gate: { 信息量: Number(v.toFixed(2)), 门槛, 挡了: true } }
3425
3394
  }
3426
3395
  gate = { 信息量: Number(v.toFixed(2)), 门槛 }
3427
3396
  }
@@ -3566,34 +3535,28 @@ function recall(data, query, now, sid) {
3566
3535
  }
3567
3536
  去重后.push(x)
3568
3537
  }
3569
- // ⭐⭐ 2026-10-27(issue #14):**常驻层** —— 长期事实不看这句话问的是什么,每轮都带上
3570
- //
3571
- // 当时的建议(转给用户那条):「把长期事实改为每轮注入,其他记忆打分淡忘」。
3572
- // 以前事实也走召回:这句话没撞上「我住在杭州」,这条就进不来 —— 可它明明是「要记一辈子」的事。
3573
- // 现在:
3574
- // · **长期事实**:每轮固定带 factsMax 条(按重要度、再按时间取前几名),
3575
- // 不看这句话问了什么,也**不占** maxItems 的位置;
3576
- // · **普通事件**:照旧走召回 + 打分淡忘,用 maxItems 个位置,且受「这趟上限」约束(不硬凑)。
3577
- //
3578
- // ⚠ 只改「谁进来、名额怎么分」,不改打分和门槛 —— 事件该不该进还是那套尺子说了算。
3579
- // ⚠ 事实按「重要度 → 时间」取,不按分数:常驻层的意义就是不依赖这句话。
3580
- const 常驻事实 = 选常驻事实(memories, scored, policy)
3538
+ // ⭐ 1.5.7:**长期事实(每轮无条件带几条)已整个删除**。
3539
+ // 理由:闲聊时把跟当下无关的记忆塞进去,会把话带跑;身份/称呼这类由星汐每轮开头带。
3540
+ // 现在只有「这句话检索命中」的记忆才会进上下文。
3581
3541
  // 普通事件:从去重后的候选里按分数拿,受 maxItems 和「这趟上限」两条约束
3582
3542
  const 事件们 = 去重后.map((x) => x.row).filter((row) => !row.m || row.m.category !== 'stable_profile')
3583
3543
  const 最高事件分 = 事件们.length && 事件们[0].s ? 事件们[0].s.C : 0
3584
3544
  const 事件本趟上限 = 事件们.length ? 分档.find(([线]) => 最高事件分 >= 线)[1] : 0
3585
3545
  const 事件名额 = Math.min(事件本趟上限, policy.maxItems)
3586
- const 条数内 = 常驻事实.concat(事件们.slice(0, 事件名额))
3546
+ const 条数内 = 事件们.slice(0, 事件名额)
3587
3547
  for (const row of 事件们.slice(事件名额)) {
3588
3548
  if (!row.omission) row.omission = 'weak' // 设置页上会写「这趟给的条数到顶了」
3589
3549
  }
3590
3550
 
3591
3551
  const { selected, 字数: 已用 } = 按字数收(条数内, policy)
3592
3552
  const ids = new Set(selected.map((r) => r.m.id))
3553
+ // ⭐ 1.5.7:以前把所有没选中的一律标成 limit,于是明明一条都没命中,第一条也显示「超过条数上限」。
3554
+ // 现在按真实原因标:在名额里但被字数卡掉 → budget;根本没进名额 → 分数不够。
3555
+ const 在名额里 = new Set(条数内.map((r) => r.m.id))
3593
3556
  for (const row of picked) {
3594
3557
  if (row.omission) continue // 别把 budget 覆盖成 limit
3595
3558
  if (ids.has(row.m.id)) continue
3596
- row.omission = 'limit'
3559
+ row.omission = 在名额里.has(row.m.id) ? 'budget' : 'below_threshold'
3597
3560
  }
3598
3561
 
3599
3562
  // ⚠ scored 要按分数排一下再交出去 —— 不然设置页那个「最近一次召回」
@@ -7284,7 +7247,6 @@ export {
7284
7247
  * ⚠ 只给测试用,不要在生产代码里调;里面都是纯函数或内部状态读取。
7285
7248
  */
7286
7249
  export const __测试 = {
7287
- 选常驻事实,
7288
7250
  后台排队,
7289
7251
  工作区目录名,
7290
7252
  扫会话目录,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fish-memory",
3
- "version": "1.5.6",
3
+ "version": "1.5.7",
4
4
  "description": "给 DSH 里的会话一份会淡忘的长期记忆,每条都带时间。",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",