@morlay/session-rdb 0.0.12 → 0.0.13
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 +64 -0
- package/dist/artifact.mjs +3 -0
- package/dist/import-D2vUnUZe.mjs +438 -0
- package/dist/import.d.mts +24 -0
- package/dist/import.mjs +2 -0
- package/dist/index-uUvn6gYp.d.mts +111 -0
- package/dist/index.d.mts +3 -203
- package/dist/index.mjs +7 -1571
- package/dist/invariant.d.mts +1 -2
- package/dist/invariant.mjs +0 -2
- package/dist/magic-string.es-BWtG0AyA.mjs +1017 -0
- package/dist/schema-B-8G4lWc.mjs +852 -0
- package/dist/schema-CFXZURX7.d.mts +116 -0
- package/dist/sqlite-DIXY-dFZ.mjs +356 -0
- package/dist/storage.d.mts +44 -0
- package/dist/storage.mjs +3 -0
- package/dist/testing.d.mts +31 -0
- package/dist/testing.mjs +16051 -0
- package/package.json +19 -2
- package/src/artifact.ts +4 -0
- package/src/index.ts +4 -0
- package/src/log.ts +86 -0
- package/src/storage.ts +5 -0
- package/src/testing/contract.ts +562 -0
- package/src/testing/coordinator-contract.ts +1723 -0
- package/src/testing/helpers.ts +20 -0
- package/src/testing.ts +12 -0
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/invariant.d.mts.map +0 -1
- package/dist/invariant.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/session-rdb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
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",
|
|
@@ -25,12 +25,16 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": "./dist/index.mjs",
|
|
28
|
+
"./artifact": "./dist/artifact.mjs",
|
|
29
|
+
"./import": "./dist/import.mjs",
|
|
28
30
|
"./invariant": "./dist/invariant.mjs",
|
|
31
|
+
"./storage": "./dist/storage.mjs",
|
|
32
|
+
"./testing": "./dist/testing.mjs",
|
|
29
33
|
"./package.json": "./package.json",
|
|
30
34
|
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
31
35
|
},
|
|
32
36
|
"dependencies": {
|
|
33
|
-
"@morlay/session-branch": "^0.0.
|
|
37
|
+
"@morlay/session-branch": "^0.0.3",
|
|
34
38
|
"drizzle-orm": "^1.0.0-rc.5-ab785fc",
|
|
35
39
|
"fflate": "^0.8.2",
|
|
36
40
|
"pg": "^8.23.0"
|
|
@@ -55,6 +59,19 @@
|
|
|
55
59
|
"patch": "./cordis.patch.yml"
|
|
56
60
|
}
|
|
57
61
|
},
|
|
62
|
+
"inlinedDependencies": {
|
|
63
|
+
"@jridgewell/sourcemap-codec": "1.6.0",
|
|
64
|
+
"@vitest/expect": "4.1.11",
|
|
65
|
+
"@vitest/pretty-format": "4.1.11",
|
|
66
|
+
"@vitest/runner": "4.1.11",
|
|
67
|
+
"@vitest/snapshot": "4.1.11",
|
|
68
|
+
"@vitest/spy": "4.1.11",
|
|
69
|
+
"@vitest/utils": "4.1.11",
|
|
70
|
+
"chai": "6.2.2",
|
|
71
|
+
"magic-string": "0.30.21",
|
|
72
|
+
"tinyrainbow": "3.1.1",
|
|
73
|
+
"vitest": "4.1.11"
|
|
74
|
+
},
|
|
58
75
|
"scripts": {
|
|
59
76
|
"build": "pnpm exec tsdown"
|
|
60
77
|
}
|
package/src/artifact.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { WriteGuard } from "./write-guard.ts";
|
|
|
27
27
|
import {
|
|
28
28
|
buildSeqMap,
|
|
29
29
|
recomputeReplaceProvenance,
|
|
30
|
+
repairOrphanInboxSplices,
|
|
30
31
|
repairSurfaceOps,
|
|
31
32
|
rowToMeta,
|
|
32
33
|
scanRows,
|
|
@@ -261,6 +262,9 @@ export class SessionPersistenceRdb
|
|
|
261
262
|
this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
|
|
262
263
|
// replace 的 provenance 读取时重计算(sourceEventSeqs 不落库)。
|
|
263
264
|
recomputeReplaceProvenance(log.events);
|
|
265
|
+
// 孤儿 inbox splice(引用已被截断排队消息的插入/消费)会使上游 Inbox
|
|
266
|
+
// 增量重放失败、整个会话不可 resume——读取时改写为 no-op 修复。
|
|
267
|
+
repairOrphanInboxSplices(log.events);
|
|
264
268
|
return {
|
|
265
269
|
meta: log.meta,
|
|
266
270
|
inheritedEventCount: SessionLogOffset(log.inheritedEventCount),
|
package/src/log.ts
CHANGED
|
@@ -283,6 +283,92 @@ export function repairSurfaceOps(events: SessionEvent[]): void {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
interface InboxSpliceLike {
|
|
287
|
+
target?: unknown;
|
|
288
|
+
start?: unknown;
|
|
289
|
+
removedCount?: unknown;
|
|
290
|
+
inserted?: Array<{ id?: unknown }>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 定位无法从空状态增量重放的 agent/inbox/spliced 事件(孤儿操作)。
|
|
295
|
+
*
|
|
296
|
+
* 上游 Inbox 每次构造都从会话起点重放全部 inbox splice,失败即拒绝整个
|
|
297
|
+
* 会话(resume / 编辑重放不可用)。rewind 截断历史轮次后若残留 splice 引用
|
|
298
|
+
* 已被截断的排队消息(插入被删、消费保留,或反之),重放时越界或重复——
|
|
299
|
+
* 无法独立重放。
|
|
300
|
+
*/
|
|
301
|
+
export function orphanInboxSpliceSeqs(events: readonly SessionEvent[]): Set<number> {
|
|
302
|
+
const inbox: Record<string, Array<{ id: string }>> = { "next-turn": [], "next-step": [] };
|
|
303
|
+
const orphan = new Set<number>();
|
|
304
|
+
for (const raw of events) {
|
|
305
|
+
// agent/inbox/spliced 属 dsh-agent 类型扩展,不在本包 SessionEventMap——
|
|
306
|
+
// 用宽类型 duck-type 读取,避免判别联合窄化到 never。
|
|
307
|
+
const event = raw as unknown as { type: string; seq: number; data: InboxSpliceLike };
|
|
308
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
309
|
+
const { target, start, removedCount, inserted } = event.data;
|
|
310
|
+
const list = typeof target === "string" ? inbox[target] : undefined;
|
|
311
|
+
if (list === undefined) {
|
|
312
|
+
orphan.add(event.seq);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const removed = removedCount ?? 0;
|
|
316
|
+
const parsedInserted = (inserted ?? []).map((m) => ({
|
|
317
|
+
id: typeof m.id === "string" ? m.id : "",
|
|
318
|
+
}));
|
|
319
|
+
if (
|
|
320
|
+
!Number.isSafeInteger(start as number) ||
|
|
321
|
+
(start as number) < 0 ||
|
|
322
|
+
(start as number) > list.length ||
|
|
323
|
+
!Number.isSafeInteger(removed as number) ||
|
|
324
|
+
(removed as number) < 0 ||
|
|
325
|
+
(start as number) + (removed as number) > list.length
|
|
326
|
+
) {
|
|
327
|
+
orphan.add(event.seq);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const candidate = [
|
|
331
|
+
...list.slice(0, start as number),
|
|
332
|
+
...parsedInserted,
|
|
333
|
+
...list.slice((start as number) + (removed as number)),
|
|
334
|
+
];
|
|
335
|
+
const other = (target === "next-turn" ? inbox["next-step"] : inbox["next-turn"]) ?? [];
|
|
336
|
+
const seen = new Set<string>();
|
|
337
|
+
let dup = false;
|
|
338
|
+
for (const m of [...candidate, ...other]) {
|
|
339
|
+
if (m.id === "") continue;
|
|
340
|
+
if (seen.has(m.id)) {
|
|
341
|
+
dup = true;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
seen.add(m.id);
|
|
345
|
+
}
|
|
346
|
+
if (dup) {
|
|
347
|
+
orphan.add(event.seq);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
list.splice(start as number, removed as number, ...parsedInserted);
|
|
351
|
+
}
|
|
352
|
+
return orphan;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** 内存修复:把孤儿 inbox splice 改写为 no-op(调用方负责持久化)。 */
|
|
356
|
+
export function repairOrphanInboxSplices(events: SessionEvent[]): void {
|
|
357
|
+
const orphan = orphanInboxSpliceSeqs(events);
|
|
358
|
+
if (orphan.size === 0) return;
|
|
359
|
+
for (const event of events) {
|
|
360
|
+
if (!orphan.has(event.seq)) continue;
|
|
361
|
+
const data = event.data as unknown as InboxSpliceLike;
|
|
362
|
+
const target = data.target;
|
|
363
|
+
(event as { data: unknown }).data = {
|
|
364
|
+
...(typeof target === "string" ? { target } : { target: "next-turn" }),
|
|
365
|
+
start: 0,
|
|
366
|
+
removedCount: 0,
|
|
367
|
+
inserted: [],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
286
372
|
export function scanRows(
|
|
287
373
|
rows: readonly EventRow[],
|
|
288
374
|
base = 0,
|