@morlay/session-rdb 0.0.15 → 0.0.16-alpha.1

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 (65) hide show
  1. package/README.md +11 -13
  2. package/dist/artifact.d.mts +30 -16
  3. package/dist/artifact.mjs +3 -3
  4. package/dist/{import-DFed8DBt.mjs → import-mZZgDBLd.mjs} +73 -83
  5. package/dist/import.d.mts +2 -3
  6. package/dist/import.mjs +2 -2
  7. package/dist/index-GdKkSSb-.d.mts +239 -0
  8. package/dist/index.d.mts +3 -3
  9. package/dist/index.mjs +763 -168
  10. package/dist/log-DBFUBPhv.mjs +394 -0
  11. package/dist/{schema-CFXZURX7.d.mts → schema-COK7-wRV.d.mts} +3 -15
  12. package/dist/sqlite-CnWwGToZ.mjs +698 -0
  13. package/dist/storage.d.mts +2 -7
  14. package/dist/storage.mjs +2 -3
  15. package/dist/testing.d.mts +30 -1
  16. package/dist/testing.mjs +676 -1991
  17. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +58 -0
  18. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +686 -0
  19. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +1 -0
  20. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +673 -0
  21. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +53 -0
  22. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +492 -0
  23. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +6 -0
  24. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +513 -0
  25. package/package.json +17 -12
  26. package/src/adapters/index.ts +10 -2
  27. package/src/adapters/to-postgres.ts +19 -15
  28. package/src/adapters/to-sqlite.ts +20 -14
  29. package/src/{entities → adapters}/types.ts +7 -8
  30. package/src/backend.ts +0 -7
  31. package/src/branch.ts +29 -110
  32. package/src/drizzle/postgres-v2.ts +11 -0
  33. package/src/drizzle/postgres-v3.ts +11 -0
  34. package/src/drizzle/sqlite-v2.ts +10 -0
  35. package/src/drizzle/sqlite-v3.ts +11 -0
  36. package/src/entities/index.ts +2 -30
  37. package/src/entities/v2/index.ts +20 -0
  38. package/src/entities/v2/session-events.ts +11 -0
  39. package/src/entities/v3/events.ts +27 -0
  40. package/src/entities/v3/index.ts +27 -0
  41. package/src/entities/v3/persistence-state.ts +10 -0
  42. package/src/entities/v3/schema-meta.ts +9 -0
  43. package/src/entities/v3/session-events.ts +26 -0
  44. package/src/entities/v3/sessions.ts +20 -0
  45. package/src/import.ts +65 -57
  46. package/src/index.ts +929 -227
  47. package/src/invariant.ts +0 -2
  48. package/src/legacy.ts +110 -0
  49. package/src/log.ts +143 -91
  50. package/src/postgres.ts +75 -93
  51. package/src/schema.ts +3 -14
  52. package/src/sqlite.ts +59 -46
  53. package/src/testing/contract.ts +460 -375
  54. package/src/testing/coordinator-contract.ts +108 -1374
  55. package/src/testing.ts +1 -6
  56. package/dist/index-uUvn6gYp.d.mts +0 -111
  57. package/dist/schema-vwzwEXNE.mjs +0 -878
  58. package/dist/sqlite-CG_Qcx5C.mjs +0 -356
  59. package/src/adapters/ddl.ts +0 -77
  60. package/src/entities/events.ts +0 -27
  61. package/src/entities/persistence-state.ts +0 -10
  62. package/src/entities/schema-meta.ts +0 -9
  63. package/src/entities/session-events.ts +0 -29
  64. package/src/entities/sessions.ts +0 -20
  65. package/src/migrate.ts +0 -137
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @morlay/session-rdb
2
2
 
3
- RDB(SQLite / PostgreSQL)持久会话后端(`ctx.sessionPersistence`):通过 drizzle
4
- 实现 `PersistenceBackend<number>`,复用上游 `PersistenceCoordinator` 与契约测试套件,
5
- 支持配置选择 SQLite 或 PostgreSQL 后端。设计细节(表结构、delta 过滤、并发写、
6
- 方言差异、仓库结构)见 [docs/design.md](docs/design.md)。
3
+ RDB(SQLite / PostgreSQL)持久会话后端(`ctx.sessionPersistence`):实现上游
4
+ `SessionHandle` 模型(`create`/`open`/`flush`/`stat`/`list`),支持配置选择
5
+ SQLite 或 PostgreSQL 后端。设计细节(表结构、原样存储、并发写、方言差异、
6
+ 仓库结构)见 [docs/design.md](docs/design.md)。
7
7
 
8
8
  ## 配置
9
9
 
@@ -59,18 +59,16 @@ type Config =
59
59
  provider 抽象并**随插件自动注册 `ctx.sessionBranch`**(`SessionBranchRdb`),
60
60
  在不修改上游代码的前提下提供 `rewind / retry / fork` 的持久化闭环:
61
61
 
62
- - **`forkFrom`**:走标准 coordinator 路径(`create` + `append`)从闭合边界
62
+ - **`forkFrom`**:走标准 handle 路径(`create` + `append`)从闭合边界
63
63
  派生新会话(纯 append,`parentSession` / `seedLength` lineage);
64
64
  - **`rewind`**:直接操作后端事务截断到闭合边界(DELETE 尾部 + head 回退 +
65
- revision bump),随后重新 `load` 同步 coordinator 状态,并更新并发写
66
- 检测 head;**支持 live 会话**(`ctx.sessions` owner 时同样就地工作:
67
- flush write-behind 缓冲 截断 RDB 截断 live 内存 log 并复位
68
- surface/header/context/derived 派生缓存 重置 agent 请求头标记
69
- 同步 coordinator cursor;不调用 `load`——live 时 load 会先 flush 撤销
70
- 截断);
65
+ revision bump),并更新并发写检测 head;**支持 live 会话**(`ctx.sessions`
66
+ owner 时同样就地工作:先 flush write-behind 缓冲 → 截断 RDB → 截断
67
+ live 内存 log 并复位 surface/header/context/derived 派生缓存重置 agent
68
+ 请求头标记对齐 handle cursor;不调用 `load`——live 时 load 会先 flush
69
+ 撤销截断);
71
70
  - **`timeline`**:`parentSession` + `seedLength` 版本树投影(live 会话含
72
- 版本效果;cold 会话因版本事件携带 `ignorable` 不进 canonical log 而只有
73
- lineage 骨架)。
71
+ 版本效果;cold 会话版本事件原样落库,效果详情可恢复)。
74
72
 
75
73
  上层编排(edit / reroll / retry / rewind / fork 完整功能)由
76
74
  `@morlay/ui-conversation-message-actions` 提供,或直接在 `ctx.sessionBranch` /
@@ -1,7 +1,7 @@
1
- import { b as EventRow, x as SessionRow } from "./schema-CFXZURX7.mjs";
2
- import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, expandProvenanceFromStorage, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
1
+ import { v as EventRow, y as SessionRow } from "./schema-COK7-wRV.mjs";
2
+ import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
3
  import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
4
- import { SessionEvent, SessionHeader, SurfaceOp } from "@deepseek-ai/dsh-session";
4
+ import { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
5
5
  //#region src/log.d.ts
6
6
  declare function rowToMeta(row: SessionRow): SessionHeader;
7
7
  declare function sessionInsertRow(storage: SessionStorageMetadata, incarnation: string): {
@@ -27,17 +27,7 @@ declare function sessionConflictRow(storage: SessionStorageMetadata): {
27
27
  fOrigin: string | null;
28
28
  fDelegationDepth: number | null;
29
29
  };
30
- declare function remapSurfaceOp(op: SurfaceOp, remap: (seq: number) => number): SurfaceOp;
31
- declare function remapShadowedRange(range: {
32
- start: number;
33
- end: number;
34
- }, remap: (seq: number) => number): {
35
- start: number;
36
- end: number;
37
- };
38
- declare function remapShadowedSeqs(seqs: readonly number[], seqMap: ReadonlyMap<number, number>): number[];
39
- declare function rowToEvent(row: EventRow, seqMap: ReadonlyMap<number, number>): SessionEvent;
40
- declare function buildSeqMap(rows: readonly Pick<EventRow, "fSequence" | "fOriginalSeq">[]): Map<number, number>;
30
+ declare function rowToEvent(row: EventRow): SessionEvent;
41
31
  /**
42
32
  * 读取时重计算 replace 的 sourceEventSeqs(sourceEventSeqs 不落库)。
43
33
  *
@@ -55,8 +45,32 @@ declare function findSurfaceRepairs(events: readonly SessionEvent[]): {
55
45
  degradeToAppend: Set<number>;
56
46
  addAppendMarker: Set<number>;
57
47
  clearSurfaceOp: Set<number>;
48
+ clampEnd: Map<number, number>;
58
49
  };
59
50
  declare function repairSurfaceOps(events: SessionEvent[]): void;
51
+ /**
52
+ * 读取时补全 assistant 结算字段:旧写入器不落库 `stream`(v2 才把流式记录
53
+ * 嵌入消息),读回时缺失即补空数组——上游 seed 校验要求 turn/step/stream
54
+ * 三者齐备,缺失会让整个会话加载失败。
55
+ */
56
+ declare function repairAssistantSettlement(events: SessionEvent[]): void;
57
+ /**
58
+ * 把紧邻 replace 的 metering 事件的 `shadowedRange` / `shadowedSeqs` 对齐到
59
+ * replace 最终的稠密 range。
60
+ *
61
+ * 旧写入器重编号事件后,metering 与 replace 一起落在旧坐标空间:二者数值
62
+ * 相等但与当前 surface 无关。夹取(或 range 本身已落在稠密空间而 metering
63
+ * 仍是旧值)后二者不再相等,而上游 token-meter 契约要求紧邻的 metering
64
+ * range 与 replace range 完全一致(否则报 no adjacent shadow price)。range
65
+ * 已一致时不改写——当前写入器保证一致,shadowedSeqs 的额外并发节点因此
66
+ * 原样保留。
67
+ */
68
+ declare function syncMeteringRanges(events: SessionEvent[]): void;
69
+ /**
70
+ * 读取视图修复总入口:结算字段补全 → surface 语义修复 → metering 对齐 →
71
+ * provenance 重算 → 孤儿 inbox splice 改写。全部只作用于内存视图,不落库。
72
+ */
73
+ declare function repairReadView(events: SessionEvent[]): void;
60
74
  /**
61
75
  * 定位无法从空状态增量重放的 agent/inbox/spliced 事件(孤儿操作)。
62
76
  *
@@ -68,10 +82,10 @@ declare function repairSurfaceOps(events: SessionEvent[]): void;
68
82
  declare function orphanInboxSpliceSeqs(events: readonly SessionEvent[]): Set<number>;
69
83
  /** 内存修复:把孤儿 inbox splice 改写为 no-op(调用方负责持久化)。 */
70
84
  declare function repairOrphanInboxSplices(events: SessionEvent[]): void;
71
- declare function scanRows(rows: readonly EventRow[], base?: number, seqMap?: ReadonlyMap<number, number>): {
85
+ declare function scanRows(rows: readonly EventRow[], base?: number): {
72
86
  preserved: SessionEvent[];
73
87
  tornFrom?: number;
74
88
  };
75
89
  declare function toJsonlArtifact(meta: SessionHeader, inheritedEventCount: number, events: readonly SessionEvent[]): string;
76
90
  //#endregion
77
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, buildSeqMap, expandProvenanceFromStorage, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, remapShadowedRange, remapShadowedSeqs, remapSurfaceOp, repairOrphanInboxSplices, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, toJsonlArtifact };
91
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, toJsonlArtifact };
package/dist/artifact.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { A as sessionInsertRow, C as remapSurfaceOp, D as rowToMeta, E as rowToEvent, O as scanRows, S as remapShadowedSeqs, T as repairSurfaceOps, _ as buildSeqMap, b as recomputeReplaceProvenance, j as toJsonlArtifact, k as sessionConflictRow, v as findSurfaceRepairs, w as repairOrphanInboxSplices, x as remapShadowedRange, y as orphanInboxSpliceSeqs } from "./schema-vwzwEXNE.mjs";
2
- import { a as parseJsonlArtifact, i as parseImportZip, n as SESSION_LOG_ARTIFACT_FILENAME, o as persistImport, r as expandProvenanceFromStorage, s as registerSessionImport, t as SESSION_IMPORT_PATH } from "./import-DFed8DBt.mjs";
3
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, buildSeqMap, expandProvenanceFromStorage, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, remapShadowedRange, remapShadowedSeqs, remapSurfaceOp, repairOrphanInboxSplices, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, toJsonlArtifact };
1
+ import { a as repairOrphanInboxSplices, c as rowToEvent, d as sessionConflictRow, f as sessionInsertRow, i as repairAssistantSettlement, l as rowToMeta, m as toJsonlArtifact, n as orphanInboxSpliceSeqs, o as repairReadView, p as syncMeteringRanges, r as recomputeReplaceProvenance, s as repairSurfaceOps, t as findSurfaceRepairs, u as scanRows } from "./log-DBFUBPhv.mjs";
2
+ import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-mZZgDBLd.mjs";
3
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, toJsonlArtifact };
@@ -1,6 +1,7 @@
1
- import { D as rowToMeta, l as isPersistedEvent } from "./schema-vwzwEXNE.mjs";
1
+ import { l as rowToMeta } from "./log-DBFUBPhv.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
- import { SESSION_FORMAT_VERSION, SessionLogOffset, decodeSeqRanges, decodeStorageRecord } from "@deepseek-ai/dsh-session";
3
+ import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
+ import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
4
5
  import { SessionBranch, SessionBranchError, balanceRewindPrefix, buildTimeline } from "@morlay/session-branch";
5
6
  import { unzipSync } from "fflate";
6
7
  //#region src/branch.ts
@@ -78,10 +79,7 @@ var SessionBranchRdbProvider = class {
78
79
  constructor(persistence, live = {
79
80
  getSession: () => void 0,
80
81
  getAgent: () => void 0,
81
- flush: async () => true,
82
- setCoordinatorCursor: () => {},
83
- setCoordinatorState: () => {},
84
- setCoordinatorSeedLength: () => {}
82
+ flush: async () => true
85
83
  }) {
86
84
  this.persistence = persistence;
87
85
  this.live = live;
@@ -95,7 +93,7 @@ var SessionBranchRdbProvider = class {
95
93
  };
96
94
  }
97
95
  async readRawEvents(id, signal) {
98
- const stored = await this.persistence.loadStored(id, signal);
96
+ const stored = await this.persistence.readLog(id, {}, signal);
99
97
  if (stored === void 0) throw new SessionBranchError(`session "${id}" not found`, "SESSION_NOT_FOUND");
100
98
  return {
101
99
  meta: stored.meta,
@@ -105,7 +103,8 @@ var SessionBranchRdbProvider = class {
105
103
  async forkFrom(sourceId, options = {}, signal) {
106
104
  signal?.throwIfAborted();
107
105
  const { atSeq, anchorMode = "after", seedSuffix = [], childSessionId, meta = {} } = options;
108
- const source = await this.persistence.inspect(sourceId, signal);
106
+ const source = await this.persistence.readLog(sourceId, {}, signal);
107
+ if (source === void 0) throw new SessionBranchError(`session "${sourceId}" not found`, "SESSION_NOT_FOUND");
109
108
  const boundary = locateTurnEnd(source.events, atSeq, anchorMode);
110
109
  const prefix = source.events.slice(0, boundary + 1);
111
110
  const childId = childSessionId ?? mintSessionId();
@@ -123,15 +122,19 @@ var SessionBranchRdbProvider = class {
123
122
  const seed = [...renumber(prefix, 0), ...renumber(seedSuffix, prefix.length)];
124
123
  const internals = this.persistence.internals();
125
124
  const sourceRows = await internals.backend.getEventRows(sourceId);
126
- const sourceEventIds = new Map(sourceRows.map((row) => [row.fOriginalSeq, row.fEventId]));
125
+ const sourceEventIds = new Map(sourceRows.map((row) => [row.fSequence, row.fEventId]));
127
126
  const reuse = /* @__PURE__ */ new Map();
128
127
  for (const event of prefix) {
129
128
  const eventId = sourceEventIds.get(event.seq);
130
129
  if (eventId !== void 0) reuse.set(event.seq, eventId);
131
130
  }
132
131
  internals.registerReuseEventIds(childId, reuse);
133
- await this.persistence.create(childMeta, prefix.length);
134
- if (seed.length > 0) await this.persistence.append(childId, seed);
132
+ const handle = await this.persistence.create(childMeta, { inheritedEventCount: SessionLogOffset(prefix.length) });
133
+ try {
134
+ if (seed.length > 0) await handle.append(seed);
135
+ } finally {
136
+ await handle.close();
137
+ }
135
138
  return childId;
136
139
  }
137
140
  async rewind(id, toBoundary, signal) {
@@ -140,7 +143,7 @@ var SessionBranchRdbProvider = class {
140
143
  const live = this.live.getSession(id);
141
144
  if (live !== void 0) await this.live.flush(live);
142
145
  const raw = live === void 0 ? await this.readRawEvents(id, signal) : void 0;
143
- const inspection = live === void 0 ? void 0 : await this.persistence.inspect(id, signal);
146
+ const inspection = live === void 0 ? void 0 : await this.persistence.internals().inspect(id, signal);
144
147
  const events = live === void 0 ? raw.events : inspection.events;
145
148
  const meta = live === void 0 ? raw.meta : inspection.meta;
146
149
  const boundaryEvent = events[toBoundary];
@@ -149,7 +152,7 @@ var SessionBranchRdbProvider = class {
149
152
  const rawKeepLength = toBoundary === -1 ? 0 : boundaryEvent.type === "turn/end" ? toBoundary + 1 : toBoundary;
150
153
  const keepLength = balanceRewindPrefix(events.slice(0, rawKeepLength)).length;
151
154
  const internals = this.persistence.internals();
152
- const denseBoundary = live === void 0 ? keepLength - 1 : events.slice(0, keepLength).filter(isPersistedEvent).length - 1;
155
+ const denseBoundary = keepLength - 1;
153
156
  const newSeedLength = await internals.backend.transaction(async (tx) => {
154
157
  signal?.throwIfAborted();
155
158
  const head = await tx.getHead(id);
@@ -169,7 +172,6 @@ var SessionBranchRdbProvider = class {
169
172
  await tx.bumpRevision(id);
170
173
  return shrunk;
171
174
  });
172
- if (newSeedLength !== null) this.live.setCoordinatorSeedLength(id, newSeedLength);
173
175
  internals.writeGuard.confirmHead(id, denseBoundary);
174
176
  if (live !== void 0) {
175
177
  truncateLiveSession(live, keepLength);
@@ -180,9 +182,9 @@ var SessionBranchRdbProvider = class {
180
182
  const phase = agent.phase;
181
183
  if (phase !== void 0) phase.lastTurn = lastTurn;
182
184
  }
183
- this.live.setCoordinatorCursor(id, keepLength);
184
- } else if (boundaryEvent?.type === "user/message") this.live.setCoordinatorState(id, keepLength, meta);
185
- else await this.persistence.load(id);
185
+ const handle = this.persistence.tracker.writerOf(id);
186
+ if (handle !== void 0) handle.resetAfterRewind(keepLength, newSeedLength === null ? void 0 : newSeedLength);
187
+ }
186
188
  const row = await internals.backend.getSession(id);
187
189
  if (row === void 0) return {
188
190
  header: {
@@ -214,29 +216,7 @@ var SessionBranchRdb = class extends SessionBranch {
214
216
  getAgent: (id) => {
215
217
  return this.ctx.get("agents")?.get(id);
216
218
  },
217
- flush: (session) => this.ctx.sessions.flush(session),
218
- setCoordinatorCursor: (id, cursor) => {
219
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(id);
220
- if (state !== void 0) state.cursor = cursor;
221
- },
222
- setCoordinatorState: (id, cursor, meta) => {
223
- const states = this.ctx.sessionPersistence.coordinator?.states;
224
- if (states === void 0) return;
225
- const state = states.get(id);
226
- if (state !== void 0) state.cursor = cursor;
227
- else states.set(id, {
228
- meta,
229
- cursor,
230
- materialized: true
231
- });
232
- },
233
- setCoordinatorSeedLength: (id, seedLength) => {
234
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(id);
235
- if (state?.storage !== void 0 && state.storage.inheritedEventCount > seedLength) state.storage = {
236
- ...state.storage,
237
- inheritedEventCount: seedLength
238
- };
239
- }
219
+ flush: (session) => this.ctx.sessions.flush(session)
240
220
  });
241
221
  readBranchPrefix(id, atSeq, mode, signal) {
242
222
  return this.provider.readBranchPrefix(id, atSeq, mode, signal);
@@ -250,22 +230,13 @@ var SessionBranchRdb = class extends SessionBranch {
250
230
  rewind(id, toBoundary, signal) {
251
231
  return this.provider.rewind(id, toBoundary, signal);
252
232
  }
253
- syncLiveCursor(sessionId) {
254
- const live = this.ctx.sessions.get(sessionId);
255
- if (live === void 0) return;
256
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(sessionId);
257
- if (state === void 0) return;
258
- let cursor = state.cursor;
259
- while (live.snapshotEvents()[cursor]?.ignorable === true) cursor += 1;
260
- state.cursor = cursor;
261
- }
262
233
  async timeline(sessionId, signal) {
263
234
  const persistence = this.ctx.sessionPersistence;
264
235
  const snapshots = await persistence.listSnapshots(signal);
265
236
  const readOwnEvents = async (id, fromSeq, s) => {
266
237
  const live = this.ctx.sessions.get(id);
267
238
  if (live !== void 0) return live.snapshotEvents().slice(fromSeq);
268
- return (await persistence.readFrom(id, fromSeq, s)).events;
239
+ return (await persistence.internals().readFrom(id, fromSeq, s)).events;
269
240
  };
270
241
  return buildTimeline(snapshots, readOwnEvents, sessionId, signal);
271
242
  }
@@ -275,16 +246,6 @@ var SessionBranchRdb = class extends SessionBranch {
275
246
  const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
276
247
  const SESSION_IMPORT_PATH = "/api/session.import";
277
248
  const MAX_IMPORT_ZIP_BYTES = 67108864;
278
- function expandProvenanceFromStorage(parsed) {
279
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("imported session records must be objects");
280
- const record = parsed;
281
- if (record.sourceEventSeqs === void 0) return parsed;
282
- if (!Number.isSafeInteger(record.seq) || record.seq < 0) throw new TypeError("imported session event seq must be a non-negative safe integer");
283
- return {
284
- ...record,
285
- sourceEventSeqs: decodeSeqRanges(record.sourceEventSeqs, record.seq + 1)
286
- };
287
- }
288
249
  function parseJsonlArtifact(content) {
289
250
  const lines = content.split("\n");
290
251
  if (lines.length === 0 || lines[0] === "") throw new Error("imported session log is empty");
@@ -294,10 +255,15 @@ function parseJsonlArtifact(content) {
294
255
  } catch {
295
256
  throw new Error("imported session log has an unparsable header line");
296
257
  }
297
- if (typeof header !== "object" || header === null || header["type"] !== "session" || typeof header["id"] !== "string" || typeof header["version"] !== "number" || !Number.isSafeInteger(header["createdAt"]) || header["createdAt"] < 0) throw new Error("imported session log has an invalid header line");
298
- const seedLength = header["seedLength"];
299
- if (seedLength !== void 0 && (!Number.isSafeInteger(seedLength) || seedLength < 0)) throw new Error("imported session log has an invalid seedLength");
300
- const events = [];
258
+ let restore;
259
+ try {
260
+ restore = sessionFormatCatalog.createRestore(header, {
261
+ recovery: "strict",
262
+ validation: "transformed"
263
+ });
264
+ } catch (error) {
265
+ throw new Error(`imported session log has an invalid header line: ${error instanceof Error ? error.message : String(error)}`);
266
+ }
301
267
  for (let i = 1; i < lines.length; i++) {
302
268
  const line = lines[i];
303
269
  if (line === void 0 || line === "") continue;
@@ -307,23 +273,33 @@ function parseJsonlArtifact(content) {
307
273
  } catch {
308
274
  throw new Error(`imported session log has an unparsable event line at ${i}`);
309
275
  }
310
- for (const event of decodeStorageRecord(expandProvenanceFromStorage(parsed))) events.push(event);
276
+ try {
277
+ restore.decodeRow(parsed);
278
+ } catch (error) {
279
+ throw new Error(`imported session log has an invalid event line at ${i}: ${error instanceof Error ? error.message : String(error)}`);
280
+ }
281
+ }
282
+ let artifact;
283
+ try {
284
+ artifact = restore.finish();
285
+ } catch (error) {
286
+ throw new Error(`imported session log is incomplete: ${error instanceof Error ? error.message : String(error)}`);
311
287
  }
288
+ const meta = artifact.header;
289
+ const events = artifact.events;
312
290
  for (let i = 0; i < events.length; i++) if (events[i].seq !== i) throw new Error(`imported session log seq gap at ${i} (got ${events[i].seq}); import requires a dense log`);
313
- const origin = header["origin"];
314
- const delegationDepth = header["delegationDepth"];
315
- const inheritedEventCount = Math.min(seedLength ?? 0, events.length);
291
+ const inheritedEventCount = Math.min(artifact.inheritedEventCount, events.length);
316
292
  return {
317
293
  meta: {
318
- version: header["version"],
319
- id: header["id"],
320
- createdAt: header["createdAt"],
321
- ...typeof header["cwd"] === "string" ? { cwd: header["cwd"] } : {},
322
- ...typeof header["parentSession"] === "string" ? { parentSession: header["parentSession"] } : {},
323
- isSeeded: seedLength !== void 0,
324
- ...origin === "subagent" ? { origin } : {},
325
- ...Number.isSafeInteger(delegationDepth) && delegationDepth > 0 ? { delegationDepth } : {},
326
- ...typeof header["agentPreset"] === "string" ? { agentPreset: header["agentPreset"] } : {}
294
+ version: SESSION_FORMAT_VERSION,
295
+ id: meta.id,
296
+ createdAt: meta.createdAt,
297
+ ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
298
+ ...meta.parentSession === void 0 ? {} : { parentSession: meta.parentSession },
299
+ isSeeded: meta.isSeeded,
300
+ ...meta.origin === void 0 ? {} : { origin: meta.origin },
301
+ ...meta.delegationDepth === void 0 ? {} : { delegationDepth: meta.delegationDepth },
302
+ ...meta.agentPreset === void 0 ? {} : { agentPreset: meta.agentPreset }
327
303
  },
328
304
  inheritedEventCount: SessionLogOffset(inheritedEventCount),
329
305
  events
@@ -345,11 +321,25 @@ async function persistImport(persistence, branch, imported, targetId, sessions)
345
321
  if (targetId !== void 0) {
346
322
  if (branch === void 0) throw new Error("sessionBranch service is unavailable");
347
323
  await branch.rewind(targetId, -1);
348
- } else await persistence.create({
349
- ...imported.meta,
350
- id
351
- }, imported.inheritedEventCount);
352
- if (imported.events.length > 0) await persistence.append(id, imported.events);
324
+ const liveHandle = persistence.tracker.writerOf(targetId);
325
+ if (liveHandle !== void 0) {
326
+ if (imported.events.length > 0) await liveHandle.append(imported.events);
327
+ } else {
328
+ const handle = await persistence.open(targetId, "write");
329
+ try {
330
+ if (imported.events.length > 0) await handle.append(imported.events);
331
+ } finally {
332
+ await handle.close();
333
+ }
334
+ }
335
+ } else {
336
+ const handle = await persistence.create({
337
+ ...imported.meta,
338
+ id
339
+ }, { inheritedEventCount: imported.inheritedEventCount });
340
+ if (imported.events.length > 0) await handle.append(imported.events);
341
+ await handle.close();
342
+ }
353
343
  if (targetId !== void 0) {
354
344
  const live = sessions?.get(targetId);
355
345
  if (live !== void 0) replaceLiveSessionLog(live, imported.events);
@@ -435,4 +425,4 @@ function registerSessionImport(ctx, persistence) {
435
425
  });
436
426
  }
437
427
  //#endregion
438
- export { parseJsonlArtifact as a, SessionBranchRdb as c, parseImportZip as i, SessionBranchRdbProvider as l, SESSION_LOG_ARTIFACT_FILENAME as n, persistImport as o, expandProvenanceFromStorage as r, registerSessionImport as s, SESSION_IMPORT_PATH as t, locateTurnEnd as u };
428
+ export { persistImport as a, SessionBranchRdbProvider as c, parseJsonlArtifact as i, locateTurnEnd as l, SESSION_LOG_ARTIFACT_FILENAME as n, registerSessionImport as o, parseImportZip as r, SessionBranchRdb as s, SESSION_IMPORT_PATH as t };
package/dist/import.d.mts CHANGED
@@ -1,11 +1,10 @@
1
- import { n as SessionPersistenceRdb } from "./index-uUvn6gYp.mjs";
1
+ import { n as SessionPersistenceRdb } from "./index-GdKkSSb-.mjs";
2
2
  import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
3
3
  import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
4
4
  import { Context } from "@deepseek-ai/cordis";
5
5
  //#region src/import.d.ts
6
6
  declare const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
7
7
  declare const SESSION_IMPORT_PATH = "/api/session.import";
8
- declare function expandProvenanceFromStorage(parsed: unknown): unknown;
9
8
  declare function parseJsonlArtifact(content: string): SessionStorageMetadata & {
10
9
  events: SessionEvent[];
11
10
  };
@@ -21,4 +20,4 @@ declare function persistImport(persistence: SessionPersistenceRdb, branch: {
21
20
  }): Promise<SessionId>;
22
21
  declare function registerSessionImport(ctx: Context, persistence: SessionPersistenceRdb): void;
23
22
  //#endregion
24
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, expandProvenanceFromStorage, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
23
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
package/dist/import.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as parseJsonlArtifact, i as parseImportZip, n as SESSION_LOG_ARTIFACT_FILENAME, o as persistImport, r as expandProvenanceFromStorage, s as registerSessionImport, t as SESSION_IMPORT_PATH } from "./import-DFed8DBt.mjs";
2
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, expandProvenanceFromStorage, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
1
+ import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-mZZgDBLd.mjs";
2
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };