sage-mem 2.0.0 → 2.1.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 +5 -5
- package/README.zh.md +5 -5
- package/lib/client.js +656 -194
- package/lib/recall/injector.js +52 -1
- package/package.json +1 -1
package/lib/recall/injector.js
CHANGED
|
@@ -26,6 +26,43 @@ import { buildCandidates, composeDelivery, composeStatus } from './compose.js'
|
|
|
26
26
|
import { composeRecallText } from './text.js'
|
|
27
27
|
import { debugEnabled, debugRecall, recordInjection } from './debug.js'
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 2.0.1:调试账里「被有效性挡掉」的两类理由(2.0.0 新引入的抑制)。
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ **只收这两个**:`below_floor` / `budget_omitted` 等老理由在 2.0.0 之前就有,
|
|
33
|
+
* 它们的解释已经在面板的「预算挡掉 N 条」与得分列表里;而失效与未裁决矛盾是
|
|
34
|
+
* 2.0.0 让记忆**从正文里消失**的唯一原因 —— 不单列出来,用户与维护侧都只能靠
|
|
35
|
+
* 猜「这条到底是没检索到,还是被新规则挡住了」。
|
|
36
|
+
*/
|
|
37
|
+
const VALIDITY_SUPPRESS_REASONS = new Set(['expired_source', 'conflict_pending'])
|
|
38
|
+
|
|
39
|
+
/** reason → 面板上的一句人话(英文由 client 侧字典决定,这里只给稳定键)。 */
|
|
40
|
+
const VALIDITY_REASON_LABEL = Object.freeze({
|
|
41
|
+
expired_source: '已失效',
|
|
42
|
+
conflict_pending: '待裁决矛盾',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 从 `decideDelivery` 的逐条处置里挑出被有效性挡掉的,附上文件名、理由与(可选)冲突 id。
|
|
47
|
+
*
|
|
48
|
+
* 纯函数,给调试账用;**不影响注入结果** —— items 是决策的产物,这里只是把其中
|
|
49
|
+
* 两类 reason 摘出来。`conflictIds` 是「文件名 → 冲突记录 id」的映射:同一份文件被
|
|
50
|
+
* 多条记录牵扯时取第一条(按 `listConflictIndex` 的时间倒序),够定位就行。
|
|
51
|
+
*/
|
|
52
|
+
function validitySuppressedItems(items, conflictIds) {
|
|
53
|
+
const out = []
|
|
54
|
+
for (const it of Array.isArray(items) ? items : []) {
|
|
55
|
+
if (!it || !VALIDITY_SUPPRESS_REASONS.has(it.reason)) continue
|
|
56
|
+
const row = { file: String(it.file ?? ''), reason: it.reason, label: VALIDITY_REASON_LABEL[it.reason] }
|
|
57
|
+
if (it.reason === 'conflict_pending' && conflictIds instanceof Map) {
|
|
58
|
+
const id = conflictIds.get(row.file)
|
|
59
|
+
if (id) row.conflictId = id
|
|
60
|
+
}
|
|
61
|
+
out.push(row)
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
29
66
|
/**
|
|
30
67
|
* @param {object} deps
|
|
31
68
|
* @param {object} deps.ctx — 宿主 ctx(`scanMemoryFiles` / `warnLog` 用)
|
|
@@ -95,6 +132,9 @@ export function createRecallInjector({ ctx, state, tools, taskIdOf }) {
|
|
|
95
132
|
// ⚠️ 只对 `contradiction` 进集合:写失败型记录里盘上那份仍是现行内容(§6.3)。
|
|
96
133
|
let conflictsForDelivery = new Set()
|
|
97
134
|
let pendingConflicts = 0
|
|
135
|
+
// 2.0.1:调试账用的「文件名 → 冲突记录 id」。只在开关打开时才读得到,但先按需
|
|
136
|
+
// 建一份空 Map(代价可忽略,比在 makeDebug 里为了 id 再扫一次队列便宜)。
|
|
137
|
+
const conflictIdByFile = new Map()
|
|
98
138
|
const validity = validityEnabled()
|
|
99
139
|
const expired = validity ? buildExpirySet(files, nowMs) : new Set()
|
|
100
140
|
if (validity) {
|
|
@@ -104,6 +144,13 @@ export function createRecallInjector({ ctx, state, tools, taskIdOf }) {
|
|
|
104
144
|
const listed = await listConflictIndex({ stateRoot: STATE_ROOT })
|
|
105
145
|
if (listed.ok) {
|
|
106
146
|
conflictsForDelivery = buildContradictionFileSet(listed.items)
|
|
147
|
+
for (const c of listed.items) {
|
|
148
|
+
if (!c || c.status !== 'open' || c.kind !== 'contradiction') continue
|
|
149
|
+
for (const key of ['file', 'otherFile']) {
|
|
150
|
+
const name = typeof c[key] === 'string' ? c[key].trim() : ''
|
|
151
|
+
if (name !== '' && !conflictIdByFile.has(name)) conflictIdByFile.set(name, c.id)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
107
154
|
pendingConflicts = listed.items.filter((c) => c.status === 'open' && c.kind === 'contradiction').length
|
|
108
155
|
} else {
|
|
109
156
|
warnLog(ctx, `读冲突队列失败,本轮不做矛盾抑制:${listed.error ?? listed.code}`, sessionId)
|
|
@@ -162,7 +209,10 @@ export function createRecallInjector({ ctx, state, tools, taskIdOf }) {
|
|
|
162
209
|
// - `sessionChars` = 本代累计正文字节(预算账);
|
|
163
210
|
// - `cardChars` = 这一步发出去的状态卡字节(没发就是 0);
|
|
164
211
|
// - `deduped` = 同题同命中集刚注过,这一步没重复追加 —— 面板上要解释成
|
|
165
|
-
//
|
|
212
|
+
// 「不是坏了,是去重生效」,否则每轮多步里大部分账看着都是空的;
|
|
213
|
+
// - `validitySuppressed`(2.0.1)= 被 2.0.0 的有效性规则挡在正文外的条目,
|
|
214
|
+
// 带 `reason`(expired_source / conflict_pending)与 `label`;矛盾还带
|
|
215
|
+
// `conflictId`,便于去「冲突」页签定位。**这不是检索失败**,是规则生效。
|
|
166
216
|
const debugOn = debugEnabled()
|
|
167
217
|
const makeDebug = debugOn
|
|
168
218
|
? (outcome) => ({
|
|
@@ -182,6 +232,7 @@ export function createRecallInjector({ ctx, state, tools, taskIdOf }) {
|
|
|
182
232
|
sessionChars: contentBytes,
|
|
183
233
|
cardChars: cardPart ? Buffer.byteLength(cardPart, 'utf8') : 0,
|
|
184
234
|
skippedByBudget: skippedForBudget,
|
|
235
|
+
validitySuppressed: validitySuppressedItems(delivered.delivery.items, conflictIdByFile),
|
|
185
236
|
card: cardPart ? 'sent' : (status.cardText ? 'deduped-or-over-budget' : 'none'),
|
|
186
237
|
})
|
|
187
238
|
: () => null
|
package/package.json
CHANGED