@morlay/session-rdb 0.0.16-alpha.0 → 0.0.16-alpha.2
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/dist/artifact.d.mts +44 -1
- package/dist/artifact.mjs +3 -3
- package/dist/{import-CKrzjXVB.mjs → import-eBSoCrxl.mjs} +1 -1
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +1 -1
- package/dist/{index-CgGDHQSK.d.mts → index-DfS0PzGM.d.mts} +17 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +105 -24
- package/dist/{log-DTJsxMud.mjs → log-BIuXv09P.mjs} +215 -12
- package/dist/{sqlite-DCy4VTD8.mjs → sqlite-Dpd17YhI.mjs} +1 -1
- package/dist/storage.mjs +1 -1
- package/dist/testing.mjs +4 -4
- package/package.json +12 -12
- package/src/index.ts +96 -33
- package/src/legacy.ts +74 -13
- package/src/log.ts +254 -13
- package/src/testing/contract.ts +1 -1
|
@@ -1,5 +1,27 @@
|
|
|
1
|
+
import { SessionSeq } from "@deepseek-ai/dsh-session";
|
|
1
2
|
import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
|
|
2
3
|
//#region src/log.ts
|
|
4
|
+
/**
|
|
5
|
+
* 把桥接行列里的 replace surfaceOp 归一到当前字段名。
|
|
6
|
+
*
|
|
7
|
+
* v2 时代落库的 JSON 用 `start`/`end`,当前格式用 `startSeq`/`endSeq`;
|
|
8
|
+
* 混合世代回退路径直接采用存储行,必须在此归一,否则上游 v3 surface 校验
|
|
9
|
+
* 以「invalid replace surfaceOp」拒绝整个会话。非 replace 形状原样返回,
|
|
10
|
+
* 由读取视图修复决定降级。
|
|
11
|
+
*/
|
|
12
|
+
function normalizeSurfaceOp(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
14
|
+
const record = value;
|
|
15
|
+
if (record["op"] !== "replace") return value;
|
|
16
|
+
const startSeq = record["startSeq"] ?? record["start"];
|
|
17
|
+
const endSeq = record["endSeq"] ?? record["end"];
|
|
18
|
+
if (typeof startSeq !== "number" || typeof endSeq !== "number") return value;
|
|
19
|
+
return {
|
|
20
|
+
op: "replace",
|
|
21
|
+
startSeq: SessionSeq(startSeq),
|
|
22
|
+
endSeq: SessionSeq(endSeq)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
3
25
|
function rowToMeta(row) {
|
|
4
26
|
if (!Number.isSafeInteger(row.fCreatedAt) || row.fCreatedAt < 0) throw new Error("stored session createdAt must be a non-negative safe integer");
|
|
5
27
|
return {
|
|
@@ -43,7 +65,7 @@ function sessionConflictRow(storage) {
|
|
|
43
65
|
};
|
|
44
66
|
}
|
|
45
67
|
function rowToEvent(row) {
|
|
46
|
-
const surfaceOp = row.fSurfaceOp !== null ? JSON.parse(row.fSurfaceOp) : void 0;
|
|
68
|
+
const surfaceOp = row.fSurfaceOp !== null ? normalizeSurfaceOp(JSON.parse(row.fSurfaceOp)) : void 0;
|
|
47
69
|
const record = JSON.parse(row.fData);
|
|
48
70
|
if (typeof record === "object" && record !== null && !Array.isArray(record) && typeof record["type"] === "string" && typeof record["seq"] === "number" && typeof record["time"] === "number" && "data" in record) return {
|
|
49
71
|
...record,
|
|
@@ -60,6 +82,7 @@ function rowToEvent(row) {
|
|
|
60
82
|
};
|
|
61
83
|
}
|
|
62
84
|
const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
85
|
+
"system/message",
|
|
63
86
|
"user/message",
|
|
64
87
|
"assistant/message",
|
|
65
88
|
"tool/result"
|
|
@@ -82,7 +105,7 @@ function recomputeReplaceProvenance(events) {
|
|
|
82
105
|
const raw = events[i];
|
|
83
106
|
const op = raw.surfaceOp;
|
|
84
107
|
if (typeof op !== "object" || op === null || op.op !== "replace") continue;
|
|
85
|
-
const {
|
|
108
|
+
const { startSeq, endSeq } = op;
|
|
86
109
|
const metering = i > 0 ? events[i - 1] : void 0;
|
|
87
110
|
const meteringData = metering !== void 0 && METERING_EVENT_TYPES.has(metering.type) ? metering.data : void 0;
|
|
88
111
|
if (meteringData?.shadowedSeqs !== void 0) {
|
|
@@ -90,7 +113,7 @@ function recomputeReplaceProvenance(events) {
|
|
|
90
113
|
continue;
|
|
91
114
|
}
|
|
92
115
|
const refs = [];
|
|
93
|
-
for (const candidate of events) if (candidate.seq >=
|
|
116
|
+
for (const candidate of events) if (candidate.seq >= startSeq && candidate.seq <= endSeq && SURFACE_EVENT_TYPES.has(candidate.type)) refs.push(candidate.seq);
|
|
94
117
|
raw.sourceEventSeqs = refs;
|
|
95
118
|
}
|
|
96
119
|
}
|
|
@@ -142,7 +165,9 @@ function findSurfaceRepairs(events) {
|
|
|
142
165
|
const degradeToAppend = /* @__PURE__ */ new Set();
|
|
143
166
|
const addAppendMarker = /* @__PURE__ */ new Set();
|
|
144
167
|
const clearSurfaceOp = /* @__PURE__ */ new Set();
|
|
145
|
-
|
|
168
|
+
const clampEnd = /* @__PURE__ */ new Map();
|
|
169
|
+
for (let index = 0; index < events.length; index++) {
|
|
170
|
+
const event = events[index];
|
|
146
171
|
const op = event.surfaceOp;
|
|
147
172
|
if (op === void 0) {
|
|
148
173
|
if (SURFACE_EVENT_TYPES.has(event.type)) {
|
|
@@ -161,12 +186,18 @@ function findSurfaceRepairs(events) {
|
|
|
161
186
|
continue;
|
|
162
187
|
}
|
|
163
188
|
const replace = typeof op === "object" && op !== null && !Array.isArray(op) ? op : void 0;
|
|
164
|
-
const start = replace?.["
|
|
165
|
-
const end = replace?.["
|
|
189
|
+
const start = replace?.["startSeq"];
|
|
190
|
+
const end = replace?.["endSeq"];
|
|
166
191
|
const shapeOk = replace !== void 0 && replace["op"] === "replace" && isEventSeqLike(start) && isEventSeqLike(end);
|
|
167
192
|
const startIdx = shapeOk ? nodes.indexOf(start) : -1;
|
|
168
|
-
|
|
193
|
+
let endIdx = shapeOk ? nodes.indexOf(end) : -1;
|
|
194
|
+
let clamped;
|
|
195
|
+
if (shapeOk && startIdx !== -1 && endIdx === -1) {
|
|
196
|
+
clamped = clampReplaceEnd(events, index, nodes, startIdx);
|
|
197
|
+
if (clamped !== void 0) endIdx = nodes.indexOf(clamped);
|
|
198
|
+
}
|
|
169
199
|
const rangeOk = shapeOk && startIdx !== -1 && endIdx !== -1 && startIdx <= endIdx;
|
|
200
|
+
const provenanceOk = event.type !== "assistant/message";
|
|
170
201
|
let rewriteOk = true;
|
|
171
202
|
if (rangeOk && event.type === "tool/result") {
|
|
172
203
|
const shadowed = nodes.slice(startIdx, endIdx + 1);
|
|
@@ -176,30 +207,202 @@ function findSurfaceRepairs(events) {
|
|
|
176
207
|
rewriteOk = original?.type === "tool/result" && toolResultRewriteContentOnly(original, event);
|
|
177
208
|
}
|
|
178
209
|
}
|
|
179
|
-
if (!rangeOk || !rewriteOk) {
|
|
210
|
+
if (!rangeOk || !rewriteOk || !provenanceOk) {
|
|
180
211
|
degradeToAppend.add(event.seq);
|
|
181
212
|
nodes.push(event.seq);
|
|
182
213
|
continue;
|
|
183
214
|
}
|
|
215
|
+
if (clamped !== void 0) clampEnd.set(event.seq, clamped);
|
|
184
216
|
nodes.splice(startIdx, endIdx - startIdx + 1, event.seq);
|
|
185
217
|
}
|
|
186
218
|
return {
|
|
187
219
|
degradeToAppend,
|
|
188
220
|
addAppendMarker,
|
|
189
|
-
clearSurfaceOp
|
|
221
|
+
clearSurfaceOp,
|
|
222
|
+
clampEnd
|
|
190
223
|
};
|
|
191
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* 夹取一个 end 落在旧坐标空间的 replace 的结尾。
|
|
227
|
+
*
|
|
228
|
+
* 仅当 replace 紧邻一个 metering 事件且其 `shadowedSeqs` 是权威数量时成立;
|
|
229
|
+
* 夹取点是从 `start` 起的第 `shadowedSeqs.length` 个当前 surface 节点(不足
|
|
230
|
+
* 则取当前 surface 末尾)。无法夹取(start 也不在当前 surface)时返回
|
|
231
|
+
* `undefined`,由调用方降级为 append。
|
|
232
|
+
*/
|
|
233
|
+
function clampReplaceEnd(events, index, nodes, startIdx) {
|
|
234
|
+
const metering = index > 0 ? events[index - 1] : void 0;
|
|
235
|
+
if (metering === void 0 || !METERING_EVENT_TYPES.has(metering.type)) return void 0;
|
|
236
|
+
const shadowedSeqs = metering.data.shadowedSeqs;
|
|
237
|
+
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return void 0;
|
|
238
|
+
return nodes[Math.min(startIdx + shadowedSeqs.length - 1, nodes.length - 1)];
|
|
239
|
+
}
|
|
192
240
|
function repairSurfaceOps(events) {
|
|
193
241
|
const repairs = findSurfaceRepairs(events);
|
|
194
|
-
if (repairs.degradeToAppend.size === 0 && repairs.addAppendMarker.size === 0 && repairs.clearSurfaceOp.size === 0) return;
|
|
242
|
+
if (repairs.degradeToAppend.size === 0 && repairs.addAppendMarker.size === 0 && repairs.clearSurfaceOp.size === 0 && repairs.clampEnd.size === 0) return;
|
|
195
243
|
for (const event of events) {
|
|
196
244
|
const raw = event;
|
|
245
|
+
const clamped = repairs.clampEnd.get(event.seq);
|
|
197
246
|
if (repairs.degradeToAppend.has(event.seq)) raw.surfaceOp = "append";
|
|
198
|
-
else if (
|
|
247
|
+
else if (clamped !== void 0) {
|
|
248
|
+
const op = raw.surfaceOp;
|
|
249
|
+
raw.surfaceOp = {
|
|
250
|
+
op: "replace",
|
|
251
|
+
startSeq: SessionSeq(op.startSeq),
|
|
252
|
+
endSeq: SessionSeq(clamped)
|
|
253
|
+
};
|
|
254
|
+
} else if (repairs.addAppendMarker.has(event.seq)) raw.surfaceOp = "append";
|
|
199
255
|
else if (repairs.clearSurfaceOp.has(event.seq)) delete raw.surfaceOp;
|
|
200
256
|
}
|
|
201
257
|
}
|
|
202
258
|
/**
|
|
259
|
+
* 读取时补全 assistant 结算字段:旧写入器不落库 `stream`(v2 才把流式记录
|
|
260
|
+
* 嵌入消息),读回时缺失即补空数组——上游 seed 校验要求 turn/step/stream
|
|
261
|
+
* 三者齐备,缺失会让整个会话加载失败。
|
|
262
|
+
*/
|
|
263
|
+
function repairAssistantSettlement(events) {
|
|
264
|
+
for (const event of events) {
|
|
265
|
+
if (event.type !== "assistant/message" && event.type !== "assistant/attempt") continue;
|
|
266
|
+
const data = event.data;
|
|
267
|
+
if (!Array.isArray(data["stream"])) data["stream"] = [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 读取时把旧格式 `request/header` 归一为当前格式:v3 起 system prompt 由
|
|
272
|
+
* surface 上的 `system/message` 承载,header 必须省略 `system`,空 `tools` /
|
|
273
|
+
* `adapterDefaults` 也必须省略(上游 v3 事件校验 fail loud)。混合世代回退
|
|
274
|
+
* 视图直接采用存储行,不归一会让整个会话加载失败。system prompt 因此不再
|
|
275
|
+
* 进入模型请求;会话下次运行由 v3 的 system/message 机制重建。
|
|
276
|
+
*/
|
|
277
|
+
function repairRequestHeaders(events) {
|
|
278
|
+
for (const event of events) {
|
|
279
|
+
if (event.type !== "request/header") continue;
|
|
280
|
+
const header = event.data["header"];
|
|
281
|
+
if (typeof header !== "object" || header === null || Array.isArray(header)) continue;
|
|
282
|
+
const record = header;
|
|
283
|
+
delete record["system"];
|
|
284
|
+
if (Array.isArray(record["tools"]) && record["tools"].length === 0) delete record["tools"];
|
|
285
|
+
const defaults = record["adapterDefaults"];
|
|
286
|
+
if (typeof defaults === "object" && defaults !== null && !Array.isArray(defaults) && Object.keys(defaults).length === 0) delete record["adapterDefaults"];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 把紧邻 replace 的 metering 事件的 `shadowedRange` / `shadowedSeqs` 对齐到
|
|
291
|
+
* replace 最终的稠密 range。
|
|
292
|
+
*
|
|
293
|
+
* 旧写入器重编号事件后,metering 与 replace 一起落在旧坐标空间:二者数值
|
|
294
|
+
* 相等但与当前 surface 无关。夹取(或 range 本身已落在稠密空间而 metering
|
|
295
|
+
* 仍是旧值)后二者不再相等,而上游 token-meter 契约要求紧邻的 metering
|
|
296
|
+
* range 与 replace range 完全一致(否则报 no adjacent shadow price)。range
|
|
297
|
+
* 已一致时不改写——当前写入器保证一致,shadowedSeqs 的额外并发节点因此
|
|
298
|
+
* 原样保留。
|
|
299
|
+
*/
|
|
300
|
+
function syncMeteringRanges(events) {
|
|
301
|
+
for (let i = 1; i < events.length; i++) {
|
|
302
|
+
const metering = events[i - 1];
|
|
303
|
+
if (!METERING_EVENT_TYPES.has(metering.type)) continue;
|
|
304
|
+
const op = events[i].surfaceOp;
|
|
305
|
+
if (typeof op !== "object" || op === null || op.op !== "replace") continue;
|
|
306
|
+
const { startSeq, endSeq } = op;
|
|
307
|
+
const data = metering.data;
|
|
308
|
+
if (data.shadowedRange?.start === startSeq && data.shadowedRange.end === endSeq) continue;
|
|
309
|
+
data.shadowedRange = {
|
|
310
|
+
start: startSeq,
|
|
311
|
+
end: endSeq
|
|
312
|
+
};
|
|
313
|
+
data.shadowedSeqs = events.filter((candidate) => candidate.seq >= startSeq && candidate.seq <= endSeq && SURFACE_EVENT_TYPES.has(candidate.type)).map((candidate) => candidate.seq);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const PTC_EVENT_RENAMES = {
|
|
317
|
+
"tool/code-dispatch-start": "tool/ptc-dispatch-start",
|
|
318
|
+
"tool/code-dispatch": "tool/ptc-dispatch"
|
|
319
|
+
};
|
|
320
|
+
/** 把消息的 `tools-code-mode` 插件来源改写为 `tools-ptc`(非该来源原样返回)。 */
|
|
321
|
+
function renamePtcMessageSource(message) {
|
|
322
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) return message;
|
|
323
|
+
const record = message;
|
|
324
|
+
const source = record["source"];
|
|
325
|
+
if (typeof source !== "object" || source === null || Array.isArray(source)) return message;
|
|
326
|
+
const sourceRecord = source;
|
|
327
|
+
if (sourceRecord["kind"] !== "plugin" || sourceRecord["plugin"] !== "tools-code-mode") return message;
|
|
328
|
+
return {
|
|
329
|
+
...record,
|
|
330
|
+
source: {
|
|
331
|
+
...sourceRecord,
|
|
332
|
+
plugin: "tools-ptc"
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* 读取时把 v2 时代的 PTC 词汇归一为当前词汇。
|
|
338
|
+
*
|
|
339
|
+
* 上游 v2→v3 迁移把 `tool/code-dispatch(-start)` 改名为
|
|
340
|
+
* `tool/ptc-dispatch(-start)`、把 `tools-code-mode` 插件来源改名为 `tools-ptc`、
|
|
341
|
+
* 把 agent preset 值 `code` 改名为 `ptc`。混合世代回退视图直接采用存储行,
|
|
342
|
+
* 不重命名会被上游 v3 校验以「unknown event type」拒绝整个会话。词汇改写
|
|
343
|
+
* 与上游迁移链的 renamePtcEvent 对齐。
|
|
344
|
+
*/
|
|
345
|
+
function renameLegacyPtcEvents(events) {
|
|
346
|
+
for (let index = 0; index < events.length; index++) {
|
|
347
|
+
const event = events[index];
|
|
348
|
+
const renamedType = PTC_EVENT_RENAMES[event.type];
|
|
349
|
+
if (renamedType !== void 0) {
|
|
350
|
+
events[index] = {
|
|
351
|
+
...event,
|
|
352
|
+
type: renamedType
|
|
353
|
+
};
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (event.type === "agent-preset/selected") {
|
|
357
|
+
const data = event.data;
|
|
358
|
+
if (data["agentPreset"] === "code") events[index] = {
|
|
359
|
+
...event,
|
|
360
|
+
data: {
|
|
361
|
+
...data,
|
|
362
|
+
agentPreset: "ptc"
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (event.type === "user/message") {
|
|
368
|
+
const renamed = renamePtcMessageSource(event.data);
|
|
369
|
+
if (renamed !== event.data) events[index] = {
|
|
370
|
+
...event,
|
|
371
|
+
data: renamed
|
|
372
|
+
};
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (event.type === "agent/inbox/spliced" || event.type === "session/title-llm-request") {
|
|
376
|
+
const data = event.data;
|
|
377
|
+
const key = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
|
|
378
|
+
const list = data[key];
|
|
379
|
+
if (!Array.isArray(list)) continue;
|
|
380
|
+
const renamed = list.map(renamePtcMessageSource);
|
|
381
|
+
if (renamed.some((message, position) => message !== list[position])) events[index] = {
|
|
382
|
+
...event,
|
|
383
|
+
data: {
|
|
384
|
+
...data,
|
|
385
|
+
[key]: renamed
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 读取视图修复总入口:形状补全(结算字段 / request header)→ PTC 词汇归一
|
|
393
|
+
* → surface 语义修复 → metering 对齐 → provenance 重算 → 孤儿 inbox splice
|
|
394
|
+
* 改写。全部只作用于内存视图,不落库。
|
|
395
|
+
*/
|
|
396
|
+
function repairReadView(events) {
|
|
397
|
+
repairAssistantSettlement(events);
|
|
398
|
+
repairRequestHeaders(events);
|
|
399
|
+
renameLegacyPtcEvents(events);
|
|
400
|
+
repairSurfaceOps(events);
|
|
401
|
+
syncMeteringRanges(events);
|
|
402
|
+
recomputeReplaceProvenance(events);
|
|
403
|
+
repairOrphanInboxSplices(events);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
203
406
|
* 定位无法从空状态增量重放的 agent/inbox/spliced 事件(孤儿操作)。
|
|
204
407
|
*
|
|
205
408
|
* 上游 Inbox 每次构造都从会话起点重放全部 inbox splice,失败即拒绝整个
|
|
@@ -311,4 +514,4 @@ function toJsonlArtifact(meta, inheritedEventCount, events) {
|
|
|
311
514
|
return lines.join("\n");
|
|
312
515
|
}
|
|
313
516
|
//#endregion
|
|
314
|
-
export {
|
|
517
|
+
export { repairAssistantSettlement as a, repairRequestHeaders as c, rowToMeta as d, scanRows as f, toJsonlArtifact as g, syncMeteringRanges as h, renameLegacyPtcEvents as i, repairSurfaceOps as l, sessionInsertRow as m, orphanInboxSpliceSeqs as n, repairOrphanInboxSplices as o, sessionConflictRow as p, recomputeReplaceProvenance as r, repairReadView as s, findSurfaceRepairs as t, rowToEvent as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { m as sessionInsertRow, p as sessionConflictRow } from "./log-BIuXv09P.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { and, eq, gte, sql } from "drizzle-orm";
|
|
4
4
|
import { check, index, integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
|
package/dist/storage.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SCHEMA_VERSION, c as eventKind, d as tSchemaMeta, f as tSessionEvents, g as WriteGuard, i as EVENT_ENCODING, l as tEvents, n as openDatabase, o as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, p as tSessions, r as DEFAULT_BUSY_TIMEOUT_MS, s as eventDimensions, t as SqliteBackend, u as tPersistenceState } from "./sqlite-
|
|
1
|
+
import { a as SCHEMA_VERSION, c as eventKind, d as tSchemaMeta, f as tSessionEvents, g as WriteGuard, i as EVENT_ENCODING, l as tEvents, n as openDatabase, o as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, p as tSessions, r as DEFAULT_BUSY_TIMEOUT_MS, s as eventDimensions, t as SqliteBackend, u as tPersistenceState } from "./sqlite-Dpd17YhI.mjs";
|
|
2
2
|
export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, SqliteBackend, WriteGuard, eventDimensions, eventKind, openDatabase, tEvents, tPersistenceState, tSchemaMeta, tSessionEvents, tSessions };
|
package/dist/testing.mjs
CHANGED
|
@@ -5116,7 +5116,7 @@ function manageArtifactAttachment(attachment) {
|
|
|
5116
5116
|
if (attachment.body != null) attachment.bodyEncoding ??= "base64";
|
|
5117
5117
|
}
|
|
5118
5118
|
//#endregion
|
|
5119
|
-
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.
|
|
5119
|
+
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.14.0_vite@8.2.2__b2fde82745a4512744fb5cfb49e8ecf4/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js
|
|
5120
5120
|
const NAME_WORKER_STATE = "__vitest_worker__";
|
|
5121
5121
|
function getWorkerState() {
|
|
5122
5122
|
const workerState = globalThis[NAME_WORKER_STATE];
|
|
@@ -10019,10 +10019,10 @@ function offsetToLineNumber(source, offset) {
|
|
|
10019
10019
|
return line + 1;
|
|
10020
10020
|
}
|
|
10021
10021
|
//#endregion
|
|
10022
|
-
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.
|
|
10022
|
+
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.14.0_vite@8.2.2__b2fde82745a4512744fb5cfb49e8ecf4/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js
|
|
10023
10023
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
10024
10024
|
//#endregion
|
|
10025
|
-
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.
|
|
10025
|
+
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.14.0_vite@8.2.2__b2fde82745a4512744fb5cfb49e8ecf4/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js
|
|
10026
10026
|
const RealDate = Date;
|
|
10027
10027
|
let now = null;
|
|
10028
10028
|
var MockDate = class MockDate extends RealDate {
|
|
@@ -10987,7 +10987,7 @@ function raceWith(promise, other) {
|
|
|
10987
10987
|
}))]);
|
|
10988
10988
|
}
|
|
10989
10989
|
//#endregion
|
|
10990
|
-
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.
|
|
10990
|
+
//#region ../../node_modules/.pnpm/vitest@4.1.11_@opentelemetry+api@1.9.1_@types+node@26.5.0_happy-dom@20.14.0_vite@8.2.2__b2fde82745a4512744fb5cfb49e8ecf4/node_modules/vitest/dist/chunks/test.DNmyFkvJ.js
|
|
10991
10991
|
var fakeTimersSrc = {};
|
|
10992
10992
|
var global$1;
|
|
10993
10993
|
var hasRequiredGlobal;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/session-rdb",
|
|
3
|
-
"version": "0.0.16-alpha.
|
|
3
|
+
"version": "0.0.16-alpha.2",
|
|
4
4
|
"description": "RDB durable session backend for DeepSeek Harness: implements both session-persistence and session-branch (rewind / retry / fork) providers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"branch",
|
|
@@ -35,27 +35,27 @@
|
|
|
35
35
|
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@morlay/session-branch": "^0.0.4-alpha.
|
|
38
|
+
"@morlay/session-branch": "^0.0.4-alpha.2",
|
|
39
39
|
"drizzle-orm": "^1.0.0-rc.5-ab785fc",
|
|
40
40
|
"fflate": "^0.8.2",
|
|
41
41
|
"pg": "^8.23.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-token-meter": "^0.1.
|
|
44
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.5-alpha.1",
|
|
46
46
|
"@types/pg": "^8.23.1",
|
|
47
47
|
"drizzle-kit": "1.0.0-rc.5-ab785fc"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
51
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-scope": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-session-format": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-session-format-catalog": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
51
|
+
"@deepseek-ai/dsh-invariants": "^0.1.5-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
|
|
53
|
+
"@deepseek-ai/dsh-scope": "^0.1.5-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-session-format": "^0.1.5-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-session-format-catalog": "^0.1.5-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.5-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-alpha.1",
|
|
59
59
|
"@deepseek-ai/schemastery": "^3.18.2"
|
|
60
60
|
},
|
|
61
61
|
"dsh": {
|
package/src/index.ts
CHANGED
|
@@ -39,14 +39,7 @@ import {
|
|
|
39
39
|
} from "@deepseek-ai/dsh-session";
|
|
40
40
|
import { type Backend, type BackendTx, type EventInsert } from "./backend.ts";
|
|
41
41
|
import { WriteGuard } from "./write-guard.ts";
|
|
42
|
-
import {
|
|
43
|
-
recomputeReplaceProvenance,
|
|
44
|
-
repairOrphanInboxSplices,
|
|
45
|
-
repairSurfaceOps,
|
|
46
|
-
rowToMeta,
|
|
47
|
-
scanRows,
|
|
48
|
-
toJsonlArtifact,
|
|
49
|
-
} from "./log.ts";
|
|
42
|
+
import { repairReadView, rowToMeta, scanRows, toJsonlArtifact } from "./log.ts";
|
|
50
43
|
import {
|
|
51
44
|
DEFAULT_BUSY_TIMEOUT_MS,
|
|
52
45
|
eventDimensions,
|
|
@@ -57,7 +50,7 @@ import { SqliteBackend } from "./sqlite.ts";
|
|
|
57
50
|
import { PostgresBackend } from "./postgres.ts";
|
|
58
51
|
import { SessionBranchRdb } from "./branch.ts";
|
|
59
52
|
import { registerSessionImport } from "./import.ts";
|
|
60
|
-
import { convertLegacyRows, isLegacyVersion } from "./legacy.ts";
|
|
53
|
+
import { adoptLegacyRows, convertLegacyRows, isLegacyVersion } from "./legacy.ts";
|
|
61
54
|
|
|
62
55
|
export { SCHEMA_VERSION } from "./schema.ts";
|
|
63
56
|
export { SessionBranchRdb, SessionBranchRdbProvider, locateTurnEnd } from "./branch.ts";
|
|
@@ -278,11 +271,10 @@ class RdbSessionHandle implements SessionHandle {
|
|
|
278
271
|
}
|
|
279
272
|
throw new SessionPersistenceNotFoundError(this.id);
|
|
280
273
|
}
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
repairOrphanInboxSplices(log.events);
|
|
274
|
+
// 读取时修复(视图只读,不落库):结算字段补全、非法 surface 替换降级
|
|
275
|
+
// 或按 metering 数量夹取、metering range 对齐、provenance 重算、孤儿
|
|
276
|
+
// inbox splice 改写。
|
|
277
|
+
repairReadView(log.events);
|
|
286
278
|
return { eventState: "detached", events: log.events.slice(offset, offset + length) };
|
|
287
279
|
}
|
|
288
280
|
|
|
@@ -568,6 +560,10 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
568
560
|
}
|
|
569
561
|
const log = await this.readLog(id, {}, options?.signal);
|
|
570
562
|
if (log === undefined) throw new SessionPersistenceNotFoundError(id);
|
|
563
|
+
// 读取视图修复必须先于校验:越界 replace(旧写入器重编号遗留的旧坐标)
|
|
564
|
+
// 降级/夹取、request/header 归一,否则上游事件校验 fail loud 拒绝整个
|
|
565
|
+
// 会话(load / open 是历史会话的加载入口,不能只依赖 handle.read)。
|
|
566
|
+
repairReadView(log.events);
|
|
571
567
|
// fail-closed:未知事件类型(非 ignorable)拒绝解释。
|
|
572
568
|
validateStoredEvents(log.meta, log.events);
|
|
573
569
|
return this.tracker.adopt(
|
|
@@ -592,7 +588,14 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
592
588
|
}
|
|
593
589
|
const log = await this.readLog(id, {}, options?.signal);
|
|
594
590
|
if (log === undefined) throw new SessionPersistenceNotFoundError(id);
|
|
591
|
+
repairReadView(log.events);
|
|
595
592
|
validateStoredEvents(log.meta, log.events);
|
|
593
|
+
// 迁移链会生成/合并事件(end-seed / attempt / chunk 合并),事件坐标与
|
|
594
|
+
// 存储桥接行数不再相等——写打开时把迁移视图整体落库,使读写同坐标;
|
|
595
|
+
// 否则 append 按存储 head 重编号会撞上已有行或写坏 log。
|
|
596
|
+
if (log.migrated && log.events.length !== log.storedCount) {
|
|
597
|
+
await this.rewriteMigratedLog(id, log);
|
|
598
|
+
}
|
|
596
599
|
// 确认 head:本实例已读该会话,后续 append 的并发校验以此为基准。
|
|
597
600
|
this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
|
|
598
601
|
return this.tracker.adopt(
|
|
@@ -669,8 +672,7 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
669
672
|
signal?.throwIfAborted();
|
|
670
673
|
const log = await this.readLog(id, {}, signal);
|
|
671
674
|
if (log === undefined) return undefined;
|
|
672
|
-
|
|
673
|
-
recomputeReplaceProvenance(log.events);
|
|
675
|
+
repairReadView(log.events);
|
|
674
676
|
const inheritedEventCount = Math.min(log.inheritedEventCount, log.events.length);
|
|
675
677
|
return {
|
|
676
678
|
meta: log.meta,
|
|
@@ -695,9 +697,9 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
695
697
|
}
|
|
696
698
|
|
|
697
699
|
/** 原样 append 落库(handle 已校验 contiguity;torn tail 先截断)。
|
|
698
|
-
* 与上游 JSONL 一致:ignorable
|
|
699
|
-
*
|
|
700
|
-
*
|
|
700
|
+
* 与上游 JSONL 一致:ignorable 事件原样存储,不做过滤。写路径校验当前
|
|
701
|
+
* 格式形状(fail-closed):未知类型(非 ignorable)与非法消息形状拒绝入库;
|
|
702
|
+
* 非当前格式(v0/v1/v2)数据只在读取时经 legacy 转换链动态转换,不落新库。 */
|
|
701
703
|
async appendBatch(
|
|
702
704
|
meta: SessionHeader,
|
|
703
705
|
inheritedEventCount: SessionLogOffset,
|
|
@@ -706,8 +708,8 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
706
708
|
): Promise<boolean> {
|
|
707
709
|
await this.ready;
|
|
708
710
|
if (events.length === 0) return false;
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
+
// 写路径当前格式校验:与读路径同契约(validateStoredEvents),保证新入库
|
|
712
|
+
// 数据只能是当前格式形状。拷贝避免 adopt 替换污染调用方数组。
|
|
711
713
|
validateStoredEvents(meta, [...events]);
|
|
712
714
|
// fork 派生会话的 seed 复用源会话事件行(不复制);消费后清除。
|
|
713
715
|
const reuse = this.reuseEventIds.get(meta.id);
|
|
@@ -762,6 +764,12 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
762
764
|
incarnation: string;
|
|
763
765
|
|
|
764
766
|
revision: number;
|
|
767
|
+
|
|
768
|
+
/** 存储桥接行数(迁移链可能生成/合并事件,与 `events.length` 不同)。 */
|
|
769
|
+
storedCount: number;
|
|
770
|
+
|
|
771
|
+
/** 是否经上游迁移链转换(非当前格式 → 当前格式)。 */
|
|
772
|
+
migrated: boolean;
|
|
765
773
|
}
|
|
766
774
|
| undefined
|
|
767
775
|
> {
|
|
@@ -776,17 +784,39 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
776
784
|
? await this.backend.getEventRows(id)
|
|
777
785
|
: await this.backend.getEventRows(id, options.fromSeq);
|
|
778
786
|
signal?.throwIfAborted();
|
|
779
|
-
//
|
|
780
|
-
//
|
|
787
|
+
// 非当前格式(v0/v1/v2)历史数据:行重建为物理记录,经上游迁移链转
|
|
788
|
+
// 当前逻辑事件。迁移链自带 seq gap / torn tail 校验(strict recovery),
|
|
789
|
+
// 无需 scanRows。混合世代 log(旧写入器跨上游版本追加)不是任何单一已
|
|
790
|
+
// 发布格式,迁移链必然拒绝——回退为当前格式视图(header 版本归一 +
|
|
791
|
+
// 读取视图修复)。
|
|
781
792
|
if (isLegacyVersion(row.fVersion)) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
793
|
+
try {
|
|
794
|
+
const converted = convertLegacyRows(row, eventRows);
|
|
795
|
+
return {
|
|
796
|
+
meta: converted.meta,
|
|
797
|
+
inheritedEventCount: converted.inheritedEventCount,
|
|
798
|
+
events: converted.events,
|
|
799
|
+
incarnation: row.fIncarnation,
|
|
800
|
+
revision: row.fRevision,
|
|
801
|
+
storedCount: eventRows.length,
|
|
802
|
+
migrated: true,
|
|
803
|
+
};
|
|
804
|
+
} catch (error: unknown) {
|
|
805
|
+
this.ctx.logger.warn(
|
|
806
|
+
`session-rdb: session "${id}" is not a single released format; adopting its stored rows as current-format data (${error instanceof Error ? error.message : String(error)})`,
|
|
807
|
+
);
|
|
808
|
+
const adopted = adoptLegacyRows(row, eventRows);
|
|
809
|
+
return {
|
|
810
|
+
meta: adopted.meta,
|
|
811
|
+
inheritedEventCount: adopted.inheritedEventCount,
|
|
812
|
+
events: adopted.events,
|
|
813
|
+
incarnation: row.fIncarnation,
|
|
814
|
+
revision: row.fRevision,
|
|
815
|
+
storedCount: eventRows.length,
|
|
816
|
+
migrated: false,
|
|
817
|
+
...(adopted.tornFrom !== undefined ? { tornFrom: adopted.tornFrom } : {}),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
790
820
|
}
|
|
791
821
|
const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
|
|
792
822
|
return {
|
|
@@ -795,10 +825,40 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
795
825
|
events: preserved,
|
|
796
826
|
incarnation: row.fIncarnation,
|
|
797
827
|
revision: row.fRevision,
|
|
828
|
+
storedCount: eventRows.length,
|
|
829
|
+
migrated: false,
|
|
798
830
|
...(tornFrom !== undefined ? { tornFrom } : {}),
|
|
799
831
|
};
|
|
800
832
|
}
|
|
801
833
|
|
|
834
|
+
/**
|
|
835
|
+
* 把迁移链读出的当前格式视图整体落库(非当前格式会话写打开时的一次性迁移)。
|
|
836
|
+
*
|
|
837
|
+
* 迁移链会生成/合并事件(end-seed / attempt / chunk 合并),事件 seq 空间
|
|
838
|
+
* 与存储桥接行数不再相等;写路径以存储 head 为锚点重编号,二者不一致会让
|
|
839
|
+
* append 撞上已有行。这里在同一事务内删光本会话桥接行、按迁移视图重建
|
|
840
|
+
* (新事件行,完整信封),并更新 head 与 revision;旧事件行保留(可能被
|
|
841
|
+
* fork 子会话引用,孤儿由惰性 GC 处理)。
|
|
842
|
+
*/
|
|
843
|
+
private async rewriteMigratedLog(
|
|
844
|
+
id: SessionId,
|
|
845
|
+
log: { meta: SessionHeader; inheritedEventCount: number; events: SessionEvent[] },
|
|
846
|
+
): Promise<void> {
|
|
847
|
+
await this.backend.transaction(async (tx) => {
|
|
848
|
+
await tx.deleteBridgeTail(id, 0);
|
|
849
|
+
await tx.upsertSession(
|
|
850
|
+
{ meta: log.meta, inheritedEventCount: SessionLogOffset(log.inheritedEventCount) },
|
|
851
|
+
randomUUID(),
|
|
852
|
+
);
|
|
853
|
+
const { headEventId, headSequence } = await appendEventTail(tx, log.meta, log.events, {
|
|
854
|
+
parentId: "",
|
|
855
|
+
nextSeq: 0,
|
|
856
|
+
});
|
|
857
|
+
await tx.updateHead(id, headEventId, headSequence);
|
|
858
|
+
await tx.bumpRevision(id);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
802
862
|
async listSnapshots(
|
|
803
863
|
signal?: AbortSignal,
|
|
804
864
|
): Promise<Array<SessionPersistenceSnapshot & { inheritedEventCount: number }>> {
|
|
@@ -873,8 +933,8 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
873
933
|
);
|
|
874
934
|
const row = await this.backend.getSession(id);
|
|
875
935
|
if (row === undefined) throw new SessionPersistenceNotFoundError(id);
|
|
876
|
-
//
|
|
877
|
-
//
|
|
936
|
+
// 非当前格式会话:meta 与继承前缀来自转换链(handle.header 已是转换后的
|
|
937
|
+
// 当前格式 header);当前格式会话用存储行。
|
|
878
938
|
if (isLegacyVersion(row.fVersion)) {
|
|
879
939
|
return {
|
|
880
940
|
meta: handle.header,
|
|
@@ -1068,6 +1128,9 @@ export class SessionPersistenceRdb extends SessionPersistence {
|
|
|
1068
1128
|
);
|
|
1069
1129
|
}
|
|
1070
1130
|
assertVersion(stored.meta);
|
|
1131
|
+
// adopt 比较必须与读取视图同源:live seed 来自修复后的读取视图(补
|
|
1132
|
+
// stream、surface 修复),未修复的存储视图会把修复差异误判为 id 冲突。
|
|
1133
|
+
repairReadView(stored.events);
|
|
1071
1134
|
const seed = session.snapshotEvents();
|
|
1072
1135
|
if (!seedCoversPrefix(seed, stored.events)) {
|
|
1073
1136
|
throw new Error(
|