dsh-prime-memory 0.22.2 → 0.24.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/CHANGELOG.en.md +401 -0
- package/CHANGELOG.ja.md +401 -0
- package/CHANGELOG.ko.md +401 -0
- package/CHANGELOG.md +100 -0
- package/dist/client.js +1180 -845
- package/dist/config.d.ts +128 -1341
- package/dist/config.js +80 -4
- package/dist/conflict-service.d.ts +4 -0
- package/dist/conflict-service.js +6 -1
- package/dist/contract.d.ts +196 -0
- package/dist/domain-gate.d.ts +11 -0
- package/dist/domain-gate.js +15 -0
- package/dist/hooks/obsolete-notice.d.ts +26 -0
- package/dist/hooks/obsolete-notice.js +64 -0
- package/dist/hooks/recall.d.ts +39 -0
- package/dist/hooks/recall.js +110 -30
- package/dist/index.d.ts +1 -1127
- package/dist/index.js +30 -2
- package/dist/memory-types.d.ts +69 -0
- package/dist/memory-types.js +165 -0
- package/dist/memport/format.d.ts +6 -1
- package/dist/memport/format.js +42 -2
- package/dist/pipeline/l1.d.ts +44 -2
- package/dist/pipeline/l1.js +163 -9
- package/dist/pipeline/md-sync.d.ts +65 -0
- package/dist/pipeline/md-sync.js +456 -0
- package/dist/pipeline/ruminate.d.ts +43 -1
- package/dist/pipeline/ruminate.js +100 -0
- package/dist/pipeline/runner.d.ts +10 -0
- package/dist/pipeline/runner.js +7 -1
- package/dist/pipeline/workspace-relink.d.ts +60 -0
- package/dist/pipeline/workspace-relink.js +73 -0
- package/dist/projection/recall.d.ts +51 -13
- package/dist/projection/recall.js +47 -12
- package/dist/prompts/examples.d.ts +73 -0
- package/dist/prompts/examples.js +178 -0
- package/dist/prompts/l1-dedup.d.ts +9 -3
- package/dist/prompts/l1-dedup.js +34 -13
- package/dist/prompts/l1-extraction.d.ts +5 -3
- package/dist/prompts/l1-extraction.js +49 -4
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +12 -1
- package/dist/stats.d.ts +12 -0
- package/dist/stats.js +327 -15
- package/dist/store/embedding-source.d.ts +8 -0
- package/dist/store/embedding-source.js +39 -2
- package/dist/store/file-versions.d.ts +2 -0
- package/dist/store/file-versions.js +2 -0
- package/dist/store/graph-store.d.ts +14 -0
- package/dist/store/graph-store.js +35 -0
- package/dist/store/l1-snapshot.d.ts +26 -1
- package/dist/store/l1-snapshot.js +53 -8
- package/dist/store/l1.d.ts +75 -3
- package/dist/store/l1.js +124 -3
- package/dist/store/memory-md-archive.d.ts +84 -0
- package/dist/store/memory-md-archive.js +155 -0
- package/dist/store/memory-md-disable.d.ts +23 -0
- package/dist/store/memory-md-disable.js +106 -0
- package/dist/store/memory-md-export.d.ts +50 -0
- package/dist/store/memory-md-export.js +200 -0
- package/dist/store/memory-md-ledger.d.ts +94 -0
- package/dist/store/memory-md-ledger.js +243 -0
- package/dist/store/memory-md-orphan.d.ts +27 -0
- package/dist/store/memory-md-orphan.js +153 -0
- package/dist/store/memory-md-parse.d.ts +35 -0
- package/dist/store/memory-md-parse.js +85 -0
- package/dist/store/memory-md-skeleton.d.ts +18 -0
- package/dist/store/memory-md-skeleton.js +61 -0
- package/dist/store/memory-md-types.d.ts +174 -0
- package/dist/store/memory-md-types.js +10 -0
- package/dist/store/memory-md.d.ts +108 -0
- package/dist/store/memory-md.js +219 -0
- package/dist/store/obsolete-notices.d.ts +39 -0
- package/dist/store/obsolete-notices.js +153 -0
- package/dist/store/recall-feedback.d.ts +75 -0
- package/dist/store/recall-feedback.js +213 -0
- package/dist/store/recall-usage.d.ts +11 -0
- package/dist/store/recall-usage.js +29 -0
- package/dist/store/session-recalled.d.ts +38 -3
- package/dist/store/session-recalled.js +156 -9
- package/dist/store/sqlite.d.ts +66 -0
- package/dist/store/sqlite.js +233 -15
- package/dist/store/supersede.d.ts +1 -1
- package/dist/store/supersede.js +1 -1
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +23 -6
- package/dist/types.d.ts +24 -7
- package/dist/types.js +10 -2
- package/dist/util/maturity.d.ts +20 -3
- package/dist/util/maturity.js +18 -1
- package/dist/util/md-name.d.ts +129 -0
- package/dist/util/md-name.js +366 -0
- package/dist/util/md-normalize.d.ts +23 -0
- package/dist/util/md-normalize.js +131 -0
- package/dist/workspace-route.d.ts +22 -0
- package/dist/workspace-route.js +66 -0
- package/dsh.plugin.json +1 -1
- package/package.json +6 -5
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* 语义(对齐"当前会话召回后对 session 有影响的条目",且**旧会话重开也有效**):
|
|
5
5
|
* - 每次召回**实际注入**(recall.ts 注入成功点)按 **sessionKey**(=agent.session?.id ?? agent.id,
|
|
6
6
|
* 与 pill 的 sessionId、投影 header.id 同源)记入;
|
|
7
|
-
* - 上下文压缩/清空(agent/created source=compact|clear)按 sessionKey
|
|
7
|
+
* - 上下文压缩/清空(agent/created source=compact|clear)按 sessionKey 处理,**两者不同**:
|
|
8
|
+
* `clear`(用户主动清空会话)整表清;`compact`(摘要替换)**不删条目**,而是把**此刻在列表里
|
|
9
|
+
* 的那批**标成「正文已被摘要取代」(`supersededIds`)——它们的正文确实离开了模型上下文,
|
|
10
|
+
* 但**已判过时的条目是人对这条记忆的裁决凭证**,删了就没了(2026-10-13 用户口径)。
|
|
11
|
+
* 所以压缩只改「这条还在不在上下文里」这一维,不动「本会话召回过什么」这条历史。
|
|
12
|
+
* 重新被注入时取代标记应当撤销(正文又回到上下文了)。
|
|
8
13
|
* - **写穿持久化到 `session-recalled.json`**(独立 sidecar,与 recall-dedupe 同款:串行原子写 +
|
|
9
14
|
* 未知版本/损坏只读降级、失败绝不抛进召回热路径)→ 重启/旧会话重开时 `init()` 载回,卡不空。
|
|
10
15
|
*
|
|
@@ -16,6 +21,23 @@ import { errDetail } from '../util/filelog.js';
|
|
|
16
21
|
import { ensureDir, readJsonStrict, rmwJson } from '../util/io.js';
|
|
17
22
|
import { SESSION_RECALLED_FILE_VERSION } from './file-versions.js';
|
|
18
23
|
import { RECALL_DEDUPE_IDS_CAP, RECALL_DEDUPE_SESSION_CAP } from './recall-dedupe.js';
|
|
24
|
+
/** 共享空集(未出现过取代的会话重复取用,不必每次新建)。 */
|
|
25
|
+
const EMPTY_SET = new Set();
|
|
26
|
+
/**
|
|
27
|
+
* 读 `injectedBy` 的宽容解析:非对象/值非字符串一律丢弃。
|
|
28
|
+
* 宁可让那条记录“无从判”(对账不动它),也不把脏值喂给可见性判定——
|
|
29
|
+
* 一个错的 message id 会把仍在本上下文里的正文误标成“已被摘要取代”。
|
|
30
|
+
*/
|
|
31
|
+
function toIdMap(raw) {
|
|
32
|
+
const out = new Map();
|
|
33
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
34
|
+
return out;
|
|
35
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
36
|
+
if (typeof v === 'string' && v.length > 0)
|
|
37
|
+
out.set(k, v);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
19
41
|
/** 条目过期清理(90 天,与 recall-dedupe 同量级)。 */
|
|
20
42
|
const PRUNE_MS = 90 * 24 * 3600_000;
|
|
21
43
|
export class SessionRecalledStore {
|
|
@@ -65,43 +87,159 @@ export class SessionRecalledStore {
|
|
|
65
87
|
continue;
|
|
66
88
|
if (now - (entry.updatedAt ?? 0) > PRUNE_MS)
|
|
67
89
|
continue;
|
|
68
|
-
this.entries.set(sid, {
|
|
90
|
+
this.entries.set(sid, {
|
|
91
|
+
ids: new Set(entry.recordIds),
|
|
92
|
+
// 旧文件没这个字段(不升版本号:升了会让老文件整体进只读降级),默认空集。
|
|
93
|
+
protectedIds: new Set(Array.isArray(entry.protectedIds) ? entry.protectedIds : []),
|
|
94
|
+
supersededIds: new Set(Array.isArray(entry.supersededIds) ? entry.supersededIds : []),
|
|
95
|
+
injectedBy: toIdMap(entry.injectedBy),
|
|
96
|
+
updatedAt: entry.updatedAt ?? now,
|
|
97
|
+
});
|
|
69
98
|
}
|
|
70
99
|
}
|
|
71
100
|
/** 记入本 sessionKey 本轮实际注入的记录 id(写穿;插入序淘汰最旧)。 */
|
|
72
|
-
add(sessionKey, recordIds) {
|
|
101
|
+
add(sessionKey, recordIds, injectionId) {
|
|
73
102
|
if (!sessionKey || recordIds.length === 0)
|
|
74
103
|
return;
|
|
75
104
|
let entry = this.entries.get(sessionKey);
|
|
76
105
|
if (!entry) {
|
|
77
|
-
entry = { ids: new Set(), updatedAt: 0 };
|
|
106
|
+
entry = { ids: new Set(), protectedIds: new Set(), supersededIds: new Set(), injectedBy: new Map(), updatedAt: 0 };
|
|
78
107
|
this.entries.set(sessionKey, entry);
|
|
79
108
|
}
|
|
109
|
+
const stamp = typeof injectionId === 'string' && injectionId.length > 0 ? injectionId : undefined;
|
|
80
110
|
for (const id of recordIds) {
|
|
81
111
|
entry.ids.add(id);
|
|
112
|
+
// 重新注入 = 正文又回到模型上下文,取代标记随之撤销(否则一条记忆只能被取代一次)。
|
|
113
|
+
entry.supersededIds.delete(id);
|
|
114
|
+
// 记下这次注入落在哪条消息上:没有它,对账就无从判“这条正文还在不在”。
|
|
115
|
+
if (stamp !== undefined)
|
|
116
|
+
entry.injectedBy.set(id, stamp);
|
|
117
|
+
// 淘汰只能淘汰**没被判过时的**:受保护条目数本身超顶时宁可超顶,也不能把人的裁决
|
|
118
|
+
// 凭证挤掉(那等于“因为后面又召回了几条,所以之前那条的判定历史丢了”)。
|
|
82
119
|
while (entry.ids.size > RECALL_DEDUPE_IDS_CAP) {
|
|
83
|
-
const oldest = entry.ids.
|
|
120
|
+
const oldest = [...entry.ids].find((x) => !entry.protectedIds.has(x));
|
|
84
121
|
if (oldest === undefined)
|
|
85
122
|
break;
|
|
86
123
|
entry.ids.delete(oldest);
|
|
124
|
+
entry.protectedIds.delete(oldest);
|
|
125
|
+
entry.supersededIds.delete(oldest);
|
|
126
|
+
entry.injectedBy.delete(oldest);
|
|
87
127
|
}
|
|
88
128
|
}
|
|
89
129
|
entry.updatedAt = Date.now();
|
|
90
130
|
this.rev++;
|
|
91
131
|
this.writeChain = this.writeChain.then(() => this.persist());
|
|
92
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* 标为受保护(判过时/软退场):条目留在列表里,且不受上限淘汰与压缩清理影响。
|
|
135
|
+
* id 不在本会话列表里时忽略(不凭这个调用新建条目)。
|
|
136
|
+
*/
|
|
137
|
+
protect(sessionKey, recordIds) {
|
|
138
|
+
const entry = this.entries.get(sessionKey);
|
|
139
|
+
if (!entry)
|
|
140
|
+
return;
|
|
141
|
+
let touched = false;
|
|
142
|
+
for (const id of recordIds) {
|
|
143
|
+
if (!entry.ids.has(id) || entry.protectedIds.has(id))
|
|
144
|
+
continue;
|
|
145
|
+
entry.protectedIds.add(id);
|
|
146
|
+
touched = true;
|
|
147
|
+
}
|
|
148
|
+
if (!touched)
|
|
149
|
+
return;
|
|
150
|
+
entry.updatedAt = Date.now();
|
|
151
|
+
this.rev++;
|
|
152
|
+
this.writeChain = this.writeChain.then(() => this.persist());
|
|
153
|
+
}
|
|
93
154
|
list(sessionKey) {
|
|
94
155
|
const entry = this.entries.get(sessionKey);
|
|
95
156
|
return entry ? [...entry.ids] : [];
|
|
96
157
|
}
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
158
|
+
/**
|
|
159
|
+
* 上下文事件:按原因区分。`'clear'`(用户主动清空)整表清;`'compact'`(摘要替换)**只打取代标记**。
|
|
160
|
+
*
|
|
161
|
+
* 旧实现两种都整表清,于是“压缩过一次”就把本会话召回历史(含已判过时的裁决凭证)抹平;
|
|
162
|
+
* 改成“压缩什么都不做”又让列表谎报“这些正文还在上下文里”。现在压缩只动一维:
|
|
163
|
+
* 把**此刻在列表里的**标为「已被摘要取代」(已在标记里的不再推序号)。
|
|
164
|
+
* 压缩仍然重置**去重与占用账本**(那两者关心的是“能不能重新注入”,与这张展示表无关)。
|
|
165
|
+
*/
|
|
166
|
+
reset(sessionKey, reason = 'clear') {
|
|
167
|
+
const entry = this.entries.get(sessionKey);
|
|
168
|
+
if (reason !== 'clear') {
|
|
169
|
+
if (entry === undefined)
|
|
170
|
+
return;
|
|
171
|
+
let touched = false;
|
|
172
|
+
for (const id of entry.ids) {
|
|
173
|
+
// 正文已经离开上下文(它就是摘要替掉的那一批)。
|
|
174
|
+
if (entry.supersededIds.has(id))
|
|
175
|
+
continue;
|
|
176
|
+
entry.supersededIds.add(id);
|
|
177
|
+
touched = true;
|
|
178
|
+
}
|
|
179
|
+
// 无变更不推序号:否则每次压缩都白写一遍盘、白推一次投影。
|
|
180
|
+
if (!touched)
|
|
181
|
+
return;
|
|
182
|
+
entry.updatedAt = Date.now();
|
|
183
|
+
this.rev++;
|
|
184
|
+
this.writeChain = this.writeChain.then(() => this.persist());
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (entry === undefined)
|
|
100
188
|
return;
|
|
101
189
|
this.entries.delete(sessionKey);
|
|
102
190
|
this.rev++;
|
|
103
191
|
this.writeChain = this.writeChain.then(() => this.persist());
|
|
104
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* 本会话「正文已被摘要取代」的 id 集(投影与端点用;只读共享。
|
|
195
|
+
* 未出现过取代的会话返回共享空集,不每次新建)。
|
|
196
|
+
*/
|
|
197
|
+
supersededOf(sessionKey) {
|
|
198
|
+
return this.entries.get(sessionKey)?.supersededIds ?? EMPTY_SET;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 用会话日志的可见性事实**对账**取代标记(Phase 0B)。
|
|
202
|
+
*
|
|
203
|
+
* 判据来自宿主官方服务的 `surface.nodes`(模型可见节点集),而不是“有没有收到一条压缩事件”:
|
|
204
|
+
* 事件会漏、换代形状还会漂移,而且漏一次就永久错、无法补算。本函数每轮从事实源重算,
|
|
205
|
+
* 所以既能**标上**也能**撤销**(重新注入、或上一轮粗标记误伤的,都能自愈)。
|
|
206
|
+
*
|
|
207
|
+
* @param visible 当前仍在上下文里的注入消息 id 集
|
|
208
|
+
* @param known 会话日志里出现过的本插件注入消息 id 集。**不在 known 里的 = 该注入从未落日志**
|
|
209
|
+
* (被宿主取消/覆盖),属“无从判”——不动它,绝不猜成“已被取代”。
|
|
210
|
+
* @returns 是否真的改动了状态(无改动不推 rev、不写盘)
|
|
211
|
+
*/
|
|
212
|
+
reconcileSuperseded(sessionKey, visible, known) {
|
|
213
|
+
const entry = this.entries.get(sessionKey);
|
|
214
|
+
if (entry === undefined || entry.injectedBy.size === 0)
|
|
215
|
+
return false;
|
|
216
|
+
let touched = false;
|
|
217
|
+
for (const [id, messageId] of entry.injectedBy) {
|
|
218
|
+
if (!entry.ids.has(id))
|
|
219
|
+
continue;
|
|
220
|
+
if (!known.has(messageId))
|
|
221
|
+
continue;
|
|
222
|
+
if (visible.has(messageId)) {
|
|
223
|
+
if (entry.supersededIds.delete(id))
|
|
224
|
+
touched = true;
|
|
225
|
+
}
|
|
226
|
+
else if (!entry.supersededIds.has(id)) {
|
|
227
|
+
entry.supersededIds.add(id);
|
|
228
|
+
touched = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!touched)
|
|
232
|
+
return false;
|
|
233
|
+
entry.updatedAt = Date.now();
|
|
234
|
+
this.rev++;
|
|
235
|
+
this.writeChain = this.writeChain.then(() => this.persist());
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 注:曾有 `remove(sessionKey, recordIds)` 供「声明过时」单条摘除,已删。
|
|
240
|
+
* 理由:用户判过时后条目得留在召回列表里显示删除线,所以**只有** `reset(…, 'clear')`
|
|
241
|
+
* 能让条目离开这张列表(判过时的还受 `protectedIds` 保护,连淘汰都走不到它)。
|
|
242
|
+
*/
|
|
105
243
|
size() {
|
|
106
244
|
return this.entries.size;
|
|
107
245
|
}
|
|
@@ -156,7 +294,16 @@ export class SessionRecalledStore {
|
|
|
156
294
|
for (const [sid, e] of this.entries) {
|
|
157
295
|
if (e.ids.size === 0)
|
|
158
296
|
continue;
|
|
159
|
-
|
|
297
|
+
// 不落这个字段 = 保护只活在内存里,重启后判过时的条目又变回可被淘汰的普通条目。
|
|
298
|
+
// 空集不写键:让没保护条目的会话与改动前的文件形状逐字一致(零漂移)。
|
|
299
|
+
// 两个可选键同口径;都为空时形状仍是 {recordIds, updatedAt}。
|
|
300
|
+
sessions[sid] = {
|
|
301
|
+
recordIds: [...e.ids],
|
|
302
|
+
...(e.protectedIds.size > 0 ? { protectedIds: [...e.protectedIds] } : {}),
|
|
303
|
+
...(e.supersededIds.size > 0 ? { supersededIds: [...e.supersededIds] } : {}),
|
|
304
|
+
...(e.injectedBy.size > 0 ? { injectedBy: Object.fromEntries(e.injectedBy) } : {}),
|
|
305
|
+
updatedAt: e.updatedAt,
|
|
306
|
+
};
|
|
160
307
|
}
|
|
161
308
|
return { version: SESSION_RECALLED_FILE_VERSION, sessions };
|
|
162
309
|
}
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -119,6 +119,21 @@ export declare class MemoryDb {
|
|
|
119
119
|
* (三处列清单:DDL / insert 语句 / 本函数),故在此留下警示。
|
|
120
120
|
*/
|
|
121
121
|
private backfillL1Fts;
|
|
122
|
+
/**
|
|
123
|
+
* 已退场 L1 行数。原本只给回灌留痕用,现在展示面也要它:
|
|
124
|
+
* `vectorCounts` 算“待补”时得分掉退场行(它们合法地无向量),否则那个数永远归不了零。
|
|
125
|
+
* 失败返回 -1——宁可日志写“未知”也不假装 0 条。
|
|
126
|
+
*/
|
|
127
|
+
countL1Retired(): number;
|
|
128
|
+
/**
|
|
129
|
+
* 清除“已退场但仍待在检索面”的残留行(FTS + 向量)。幂等:第二次跑命中 0 行。
|
|
130
|
+
*
|
|
131
|
+
* 为什么必须存在于启动路径:口径修复只挡住**新增**的脏行,而存量脏行仍在给
|
|
132
|
+
* BM25/向量检索供货——对用户而言就是“标了失效却还能被召回”。
|
|
133
|
+
* 失败只 warn:这是个修复动作,无权把库拖进 degraded,更不能抛进启动
|
|
134
|
+
* 的 await 链(那里报错 = storageOk=false = 记忆功能整体停用)。
|
|
135
|
+
*/
|
|
136
|
+
private purgeRetiredRetrievalRows;
|
|
122
137
|
/** 重建后的 l0_fts 从 l0_conversations 全量回灌(仅 drop 重建时调用;iterate 流式)。 */
|
|
123
138
|
private backfillL0Fts;
|
|
124
139
|
private readEmbeddingMeta;
|
|
@@ -227,6 +242,57 @@ export declare class MemoryDb {
|
|
|
227
242
|
repoKeyName?: string;
|
|
228
243
|
repoKeyOwner?: string;
|
|
229
244
|
}, expectRepoKeyName?: string): number;
|
|
245
|
+
/**
|
|
246
|
+
* 批量写 `workspace_id`(内容路由 / 存量 re-link 用)。单事务,**刻意不碰 `updated_time`**。
|
|
247
|
+
*
|
|
248
|
+
* 为什么不碰 `updated_time`(三条都是硬约束):
|
|
249
|
+
* ① 它在 `canonicalRecords` 的哈希字段表里——动了就是“静默改写”,会让改动前建的快照
|
|
250
|
+
* 永久 verify 不通过(`workspace_id` 本身不在表里,所以**只**写它才是快照安全的);
|
|
251
|
+
* ② 它是时效衰减(agingWeight)与列表排序的输入,改一下归属顺手把记忆“刷陈旧”是意外副作用;
|
|
252
|
+
* ③ re-link 按固定序分页扫,不写它才不会边扫边漂。
|
|
253
|
+
*
|
|
254
|
+
* 为什么连 `l1_fts` 一起改:FTS 行**自带** `workspace_id`(UNINDEXED 随行),
|
|
255
|
+
* 而检索过滤 `(? = '' OR scope='global' OR workspace_id = ?)` 读的就是 FTS 行那一列。
|
|
256
|
+
* 只改主表 = 一旦启用围栏,关键词路按旧归属过滤、向量路按新归属过滤,两路结果不一致。
|
|
257
|
+
* 退场行不在 FTS 里,那条 UPDATE 自然命中 0 行,不是错误。
|
|
258
|
+
*/
|
|
259
|
+
setL1WorkspaceIds(items: ReadonlyArray<{
|
|
260
|
+
id: string;
|
|
261
|
+
workspaceId: string;
|
|
262
|
+
}>): number;
|
|
263
|
+
/**
|
|
264
|
+
* 改一条记录的文件身份名(N1)。三态返回,**不碰 `updated_time`**:它在快照哈希
|
|
265
|
+
* 字段表里、又是衰减与排序的输入——改名不是“内容变了”,不该顺带把这条记成刚更新过
|
|
266
|
+
* (与 `setL1WorkspaceIds` 同一个理由)。`name=''` 是合法值(= 没名字,部分索引不约束空串)。
|
|
267
|
+
*
|
|
268
|
+
* 唯一性由 `l1_records_name_uidx` 挡:撞名 ⇒ `'dup'`,由调用方决定报错还是让用户先改
|
|
269
|
+
* 另一条——**不自动加后缀**,那会静默造出语义重叠的第二条记忆。
|
|
270
|
+
* (另一个口径:机器给**本来没名字**的记录起名时必须消歧,见 `renameL1Batch` 的注释。)
|
|
271
|
+
*/
|
|
272
|
+
renameL1(id: string, name: string): 'ok' | 'dup' | 'missing' | 'failed';
|
|
273
|
+
/**
|
|
274
|
+
* 批量写 `name`(单事务)。给“机器起名”用:存量记录没名字时一次补齐。
|
|
275
|
+
*
|
|
276
|
+
* 与 `renameL1` 的分工不是性能而是**语义**:那里是**人给的**名字,撞了就报错让用户
|
|
277
|
+
* 先改另一条,绝不静默加后缀;这里是机器给**本来没名字**的记录派生名字,同正文的
|
|
278
|
+
* 多条必然撞名(真机实测 42 条挤进 17 个路径),**不消歧就是每轮导出重刷 + 反向同步
|
|
279
|
+
* 把一份内容回灌给多条**。所以消歧必须在本层之上先把名字算唯一,本层只负责落库。
|
|
280
|
+
*
|
|
281
|
+
* 与 `renameL1` 同样**不碰 `updated_time`**(起名不是“内容变了”),也不动 FTS——
|
|
282
|
+
* `name` 不在检索面里(`l1_fts` 没这一列)。
|
|
283
|
+
*
|
|
284
|
+
* @param items - `{ id, name }`,name 已由调用方保证全库唯一。
|
|
285
|
+
* @returns 实际写入的 id 集(现值已等于目标值的跳过——幂等)与失败集。
|
|
286
|
+
*/
|
|
287
|
+
renameL1Batch(items: ReadonlyArray<{
|
|
288
|
+
id: string;
|
|
289
|
+
name: string;
|
|
290
|
+
}>): {
|
|
291
|
+
done: string[];
|
|
292
|
+
failed: string[];
|
|
293
|
+
};
|
|
294
|
+
/** 按文件身份名反查 record_id(重名预检与 md 回灌用);没这条名 ⇒ undefined。*/
|
|
295
|
+
findRecordIdByName(name: string): string | undefined;
|
|
230
296
|
/** 入队一个重聚类作业(demote-to-wiki / repo 归属变化触发;source 区分来源)。 */
|
|
231
297
|
enqueueSceneRecluster(family: string, sceneNames: readonly string[], batchId: string, source?: string): string;
|
|
232
298
|
/** 取一个待处理作业(ruminate 空闲消费;按创建序)。 */
|