@morlay/session-rdb 0.0.12 → 0.0.14

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/index.mjs CHANGED
@@ -1,1145 +1,13 @@
1
+ import { i as WriteGuard, r as createTablesSql, t as SqliteBackend } from "./sqlite-BUWnS0so.mjs";
2
+ import { A as sessionInsertRow, D as rowToMeta, O as scanRows, T as repairSurfaceOps, _ as buildSeqMap, a as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, b as recomputeReplaceProvenance, g as toPostgresSchema, i as SCHEMA_VERSION, j as toJsonlArtifact, k as sessionConflictRow, l as isPersistedEvent, m as postgresTableDefs, n as EPHEMERAL_EVENT_TYPES, o as eventDimensions, r as EVENT_ENCODING, t as DEFAULT_BUSY_TIMEOUT_MS, w as repairOrphanInboxSplices } from "./schema-BfPVmr1X.mjs";
3
+ import { c as SessionBranchRdb, l as SessionBranchRdbProvider, s as registerSessionImport, u as locateTurnEnd } from "./import-B-tf5vQn.mjs";
1
4
  import z from "@deepseek-ai/schemastery";
2
5
  import { randomUUID } from "node:crypto";
3
6
  import { Pool } from "pg";
4
7
  import { drizzle } from "drizzle-orm/node-postgres";
5
8
  import { PersistenceCoordinator, SessionPersistence, SessionPersistenceRevision } from "@deepseek-ai/dsh-session-persistence";
6
- import { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq, decodeSeqRanges, decodeStorageRecord, encodeSeqRanges, packChunkRuns } from "@deepseek-ai/dsh-session";
9
+ import { SessionLogOffset } from "@deepseek-ai/dsh-session";
7
10
  import { and, desc, eq, gte, sql } from "drizzle-orm";
8
- import { check, index, integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
9
- import { bigint, check as check$1, index as index$1, integer as integer$1, pgSchema, pgTable, serial, text as text$1, unique as unique$1 } from "drizzle-orm/pg-core";
10
- import { statSync } from "node:fs";
11
- import { mkdir, open } from "node:fs/promises";
12
- import { dirname, resolve } from "node:path";
13
- import { DatabaseSync } from "node:sqlite";
14
- import { drizzle as drizzle$1 } from "drizzle-orm/node-sqlite";
15
- import { SessionBranch, SessionBranchError, balanceRewindPrefix, buildTimeline } from "@morlay/session-branch";
16
- import { unzipSync } from "fflate";
17
- //#region src/write-guard.ts
18
- var WriteGuard = class {
19
- headSeqs = /* @__PURE__ */ new Map();
20
- confirmHead(id, head) {
21
- this.headSeqs.set(id, head);
22
- }
23
- assertNoConcurrentWriter(id, storedHead) {
24
- const known = this.headSeqs.get(id);
25
- if (known === void 0) {
26
- if (storedHead !== -1) throw new Error(`session "${id}" has a persisted log this instance has not read; another writer may own it — load the session first`);
27
- return;
28
- }
29
- if (known !== storedHead) throw new Error(`session "${id}" was modified by another writer (stored head ${storedHead}, this instance last confirmed head ${known}); concurrent writers on one session are not supported`);
30
- }
31
- };
32
- //#endregion
33
- //#region src/log.ts
34
- function rowToMeta(row) {
35
- if (!Number.isSafeInteger(row.fCreatedAt) || row.fCreatedAt < 0) throw new Error("stored session createdAt must be a non-negative safe integer");
36
- return {
37
- version: row.fVersion,
38
- id: row.fSessionId,
39
- createdAt: row.fCreatedAt,
40
- ...row.fCwd !== null ? { cwd: row.fCwd } : {},
41
- ...row.fParentSession !== null ? { parentSession: row.fParentSession } : {},
42
- isSeeded: row.fSeedLength !== null,
43
- ...row.fOrigin !== null ? { origin: row.fOrigin } : {},
44
- ...row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth }
45
- };
46
- }
47
- function sessionInsertRow(storage, incarnation) {
48
- const meta = storage.meta;
49
- return {
50
- fSessionId: meta.id,
51
- fHeadEventId: "",
52
- fHeadSequence: -1,
53
- fVersion: meta.version,
54
- fCreatedAt: meta.createdAt,
55
- fCwd: meta.cwd ?? null,
56
- fParentSession: meta.parentSession ?? null,
57
- fSeedLength: meta.isSeeded ? storage.inheritedEventCount : null,
58
- fOrigin: meta.origin ?? null,
59
- fDelegationDepth: meta.delegationDepth ?? null,
60
- fIncarnation: incarnation,
61
- fRevision: 0
62
- };
63
- }
64
- function sessionConflictRow(storage) {
65
- const meta = storage.meta;
66
- return {
67
- fVersion: meta.version,
68
- fCreatedAt: meta.createdAt,
69
- fCwd: meta.cwd ?? null,
70
- fParentSession: meta.parentSession ?? null,
71
- fSeedLength: meta.isSeeded ? storage.inheritedEventCount : null,
72
- fOrigin: meta.origin ?? null,
73
- fDelegationDepth: meta.delegationDepth ?? null
74
- };
75
- }
76
- function remapSurfaceOp(op, remap) {
77
- if (op === "append") return op;
78
- return {
79
- op: "replace",
80
- start: SessionSeq(remap(op.start)),
81
- end: SessionSeq(remap(op.end))
82
- };
83
- }
84
- function remapShadowedRange(range, remap) {
85
- return {
86
- start: remap(range.start),
87
- end: remap(range.end)
88
- };
89
- }
90
- function rowToEvent(row, seqMap) {
91
- const remap = (seq) => seqMap.get(seq) ?? seq;
92
- const surfaceOp = row.fSurfaceOp !== null ? remapSurfaceOp(JSON.parse(row.fSurfaceOp), remap) : void 0;
93
- const data = JSON.parse(row.fData);
94
- if (row.fType === "compaction/summary" || row.fType === "compaction/prune") {
95
- const metering = data;
96
- if (metering.shadowedRange !== void 0) metering.shadowedRange = remapShadowedRange(metering.shadowedRange, remap);
97
- }
98
- return {
99
- type: row.fType,
100
- seq: row.fSequence,
101
- time: row.fCreatedAt,
102
- data,
103
- ...surfaceOp === void 0 ? {} : { surfaceOp }
104
- };
105
- }
106
- function buildSeqMap(rows) {
107
- const map = /* @__PURE__ */ new Map();
108
- for (const row of rows) if (!map.has(row.fOriginalSeq)) map.set(row.fOriginalSeq, row.fSequence);
109
- return map;
110
- }
111
- const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
112
- "user/message",
113
- "assistant/message",
114
- "tool/result"
115
- ]);
116
- function recomputeReplaceProvenance(events) {
117
- for (const event of events) {
118
- const raw = event;
119
- const op = raw.surfaceOp;
120
- if (typeof op !== "object" || op === null || op.op !== "replace") continue;
121
- const { start, end } = op;
122
- const refs = [];
123
- for (const candidate of events) if (candidate.seq >= start && candidate.seq <= end && SURFACE_EVENT_TYPES.has(candidate.type)) refs.push(candidate.seq);
124
- raw.sourceEventSeqs = refs;
125
- }
126
- }
127
- function isEventSeqLike(value) {
128
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
129
- }
130
- function isDeepEqualJson(a, b) {
131
- if (a === b) return true;
132
- if (Array.isArray(a) || Array.isArray(b)) {
133
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
134
- return a.every((item, i) => isDeepEqualJson(item, b[i]));
135
- }
136
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
137
- const aKeys = Object.keys(a);
138
- const bRecord = b;
139
- if (aKeys.length !== Object.keys(bRecord).length) return false;
140
- return aKeys.every((key) => Object.hasOwn(bRecord, key) && isDeepEqualJson(a[key], bRecord[key]));
141
- }
142
- function toolResultRewriteContentOnly(original, replacement) {
143
- const originalData = original.data;
144
- const replacementData = replacement.data;
145
- const originalMessage = originalData["message"];
146
- const replacementMessage = replacementData["message"];
147
- const originalContent = Array.isArray(originalMessage?.content) ? originalMessage.content : void 0;
148
- const replacementContent = Array.isArray(replacementMessage?.content) ? replacementMessage.content : void 0;
149
- if (originalContent === void 0 || replacementContent === void 0) return false;
150
- return isDeepEqualJson({
151
- ...originalData,
152
- message: {
153
- ...originalMessage,
154
- content: [{
155
- ...originalContent[0],
156
- content: null
157
- }]
158
- }
159
- }, {
160
- ...replacementData,
161
- message: {
162
- ...replacementMessage,
163
- content: [{
164
- ...replacementContent[0],
165
- content: null
166
- }]
167
- }
168
- });
169
- }
170
- function findSurfaceRepairs(events) {
171
- const nodes = [];
172
- const degradeToAppend = /* @__PURE__ */ new Set();
173
- const addAppendMarker = /* @__PURE__ */ new Set();
174
- const clearSurfaceOp = /* @__PURE__ */ new Set();
175
- for (const event of events) {
176
- const op = event.surfaceOp;
177
- if (op === void 0) {
178
- if (SURFACE_EVENT_TYPES.has(event.type)) {
179
- addAppendMarker.add(event.seq);
180
- nodes.push(event.seq);
181
- }
182
- continue;
183
- }
184
- if (op === "append") {
185
- if (SURFACE_EVENT_TYPES.has(event.type)) nodes.push(event.seq);
186
- else clearSurfaceOp.add(event.seq);
187
- continue;
188
- }
189
- if (!SURFACE_EVENT_TYPES.has(event.type)) {
190
- clearSurfaceOp.add(event.seq);
191
- continue;
192
- }
193
- const replace = typeof op === "object" && op !== null && !Array.isArray(op) ? op : void 0;
194
- const start = replace?.["start"];
195
- const end = replace?.["end"];
196
- const shapeOk = replace !== void 0 && replace["op"] === "replace" && isEventSeqLike(start) && isEventSeqLike(end);
197
- const startIdx = shapeOk ? nodes.indexOf(start) : -1;
198
- const endIdx = shapeOk ? nodes.indexOf(end) : -1;
199
- const rangeOk = shapeOk && startIdx !== -1 && endIdx !== -1 && startIdx <= endIdx;
200
- let rewriteOk = true;
201
- if (rangeOk && event.type === "tool/result") {
202
- const shadowed = nodes.slice(startIdx, endIdx + 1);
203
- if (shadowed.length !== 1) rewriteOk = false;
204
- else {
205
- const original = events[shadowed[0]];
206
- rewriteOk = original?.type === "tool/result" && toolResultRewriteContentOnly(original, event);
207
- }
208
- }
209
- if (!rangeOk || !rewriteOk) {
210
- degradeToAppend.add(event.seq);
211
- nodes.push(event.seq);
212
- continue;
213
- }
214
- nodes.splice(startIdx, endIdx - startIdx + 1, event.seq);
215
- }
216
- return {
217
- degradeToAppend,
218
- addAppendMarker,
219
- clearSurfaceOp
220
- };
221
- }
222
- function repairSurfaceOps(events) {
223
- const repairs = findSurfaceRepairs(events);
224
- if (repairs.degradeToAppend.size === 0 && repairs.addAppendMarker.size === 0 && repairs.clearSurfaceOp.size === 0) return;
225
- for (const event of events) {
226
- const raw = event;
227
- if (repairs.degradeToAppend.has(event.seq)) raw.surfaceOp = "append";
228
- else if (repairs.addAppendMarker.has(event.seq)) raw.surfaceOp = "append";
229
- else if (repairs.clearSurfaceOp.has(event.seq)) delete raw.surfaceOp;
230
- }
231
- }
232
- function scanRows(rows, base = 0, seqMap = /* @__PURE__ */ new Map()) {
233
- const parsed = rows.map((row) => {
234
- try {
235
- return {
236
- ok: true,
237
- event: rowToEvent(row, seqMap)
238
- };
239
- } catch {
240
- return { ok: false };
241
- }
242
- });
243
- let lastTurnEnd = -1;
244
- for (let i = parsed.length - 1; i >= 0; i--) if (parsed[i]?.ok && rows[i]?.fType === "turn/end") {
245
- lastTurnEnd = i;
246
- break;
247
- }
248
- const preserved = [];
249
- for (let i = 0; i < rows.length; i++) {
250
- const p = parsed[i];
251
- if (!p?.ok || p.event === void 0) {
252
- if (i <= lastTurnEnd) throw new Error(`corrupt session log: unparsable committed event at seq ${rows[i]?.fSequence}`);
253
- break;
254
- }
255
- if (p.event.seq !== base + i) {
256
- if (i <= lastTurnEnd) throw new Error(`corrupt session log: seq gap in committed region (expected ${base + i}, got ${p.event.seq})`);
257
- break;
258
- }
259
- preserved.push(p.event);
260
- }
261
- return preserved.length < rows.length ? {
262
- preserved,
263
- tornFrom: base + preserved.length
264
- } : { preserved };
265
- }
266
- function toStorageRecord(record) {
267
- const withSeqs = record;
268
- if (withSeqs.sourceEventSeqs === void 0) return record;
269
- return {
270
- ...record,
271
- sourceEventSeqs: encodeSeqRanges(withSeqs.sourceEventSeqs)
272
- };
273
- }
274
- function toJsonlArtifact(meta, inheritedEventCount, events) {
275
- const header = {
276
- type: "session",
277
- version: meta.version,
278
- id: meta.id,
279
- createdAt: meta.createdAt,
280
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
281
- ...meta.parentSession === void 0 ? {} : { parentSession: meta.parentSession },
282
- ...meta.isSeeded ? { seedLength: inheritedEventCount } : {},
283
- ...meta.origin === void 0 ? {} : { origin: meta.origin },
284
- delegationDepth: meta.delegationDepth ?? 0,
285
- ...meta.agentPreset === void 0 ? {} : { agentPreset: meta.agentPreset }
286
- };
287
- const lines = [JSON.stringify(header)];
288
- for (const record of packChunkRuns(events)) lines.push(JSON.stringify(toStorageRecord(record)));
289
- return lines.join("\n");
290
- }
291
- //#endregion
292
- //#region src/entities/types.ts
293
- function toProperty(name) {
294
- return name.replace(/_([a-z])/g, (_match, char) => char.toUpperCase());
295
- }
296
- //#endregion
297
- //#region src/adapters/to-sqlite.ts
298
- function buildColumn$1(c, tables) {
299
- let col;
300
- switch (c.type) {
301
- case "text":
302
- col = text(c.name);
303
- break;
304
- case "serial":
305
- col = integer(c.name).primaryKey({ autoIncrement: true });
306
- break;
307
- case "integer":
308
- case "bigint": {
309
- const built = integer(c.name);
310
- col = c.primaryKey ? built.primaryKey() : built;
311
- break;
312
- }
313
- }
314
- if (c.notNull) col = col.notNull();
315
- if (c.default !== void 0) col = col.default(c.default);
316
- if (c.unique) col = col.unique();
317
- if (c.references) {
318
- const { table, column, onDelete } = c.references;
319
- col = col.references(() => tables[table][toProperty(column)], { onDelete });
320
- }
321
- return col;
322
- }
323
- function toSqliteSchema(defs) {
324
- const tables = {};
325
- for (const def of defs) {
326
- const columns = {};
327
- for (const c of def.columns) columns[toProperty(c.name)] = buildColumn$1(c, tables);
328
- const extra = (self) => [
329
- ...(def.checks ?? []).map((c) => check(c.name, sql.raw(c.expression))),
330
- ...(def.uniques ?? []).map((u) => unique(u.name).on(...u.columns.map((name) => self[toProperty(name)]))),
331
- ...(def.indexes ?? []).map((i) => index(i.name).on(...i.columns.map((name) => self[toProperty(name)])))
332
- ];
333
- tables[def.name] = sqliteTable(def.name, columns, extra);
334
- }
335
- return tables;
336
- }
337
- //#endregion
338
- //#region src/adapters/to-postgres.ts
339
- function buildColumn(c, tables) {
340
- let col;
341
- switch (c.type) {
342
- case "text":
343
- col = text$1(c.name);
344
- break;
345
- case "serial":
346
- col = serial(c.name).primaryKey();
347
- break;
348
- case "integer": {
349
- const built = integer$1(c.name);
350
- col = c.primaryKey ? built.primaryKey() : built;
351
- break;
352
- }
353
- case "bigint": {
354
- const built = bigint(c.name, { mode: "number" });
355
- col = c.primaryKey ? built.primaryKey() : built;
356
- break;
357
- }
358
- }
359
- if (c.notNull) col = col.notNull();
360
- if (c.default !== void 0) col = col.default(c.default);
361
- if (c.unique) col = col.unique();
362
- if (c.references) {
363
- const { table, column, onDelete } = c.references;
364
- col = col.references(() => tables[table][toProperty(column)], { onDelete });
365
- }
366
- return col;
367
- }
368
- function toPostgresSchema(defs, schemaName = "public") {
369
- const tables = {};
370
- const table = schemaName === "public" ? pgTable : pgSchema(schemaName).table;
371
- for (const def of defs) {
372
- const columns = {};
373
- for (const c of def.columns) columns[toProperty(c.name)] = buildColumn(c, tables);
374
- const extra = (self) => [
375
- ...(def.checks ?? []).map((c) => check$1(c.name, sql.raw(c.expression))),
376
- ...(def.uniques ?? []).map((u) => unique$1(u.name).on(...u.columns.map((name) => self[toProperty(name)]))),
377
- ...(def.indexes ?? []).map((i) => index$1(i.name).on(...i.columns.map((name) => self[toProperty(name)])))
378
- ];
379
- tables[def.name] = table(def.name, columns, extra);
380
- }
381
- return tables;
382
- }
383
- //#endregion
384
- //#region src/adapters/ddl.ts
385
- function sqlType(dialect, type) {
386
- switch (type) {
387
- case "serial": return dialect === "sqlite" ? "INTEGER" : "SERIAL";
388
- case "integer": return "INTEGER";
389
- case "bigint": return dialect === "sqlite" ? "INTEGER" : "BIGINT";
390
- case "text": return "TEXT";
391
- }
392
- }
393
- function literal(value) {
394
- return typeof value === "string" ? `'${value.replace(/'/g, "''")}'` : String(value);
395
- }
396
- function quote(name) {
397
- return `"${name}"`;
398
- }
399
- function columnSql(dialect, c) {
400
- let sql = `${quote(c.name)} ${sqlType(dialect, c.type)}`;
401
- if (c.primaryKey) sql += " PRIMARY KEY";
402
- if (c.type === "serial" && dialect === "sqlite") sql += " AUTOINCREMENT";
403
- if (c.notNull) sql += " NOT NULL";
404
- if (c.default !== void 0) sql += ` DEFAULT ${literal(c.default)}`;
405
- if (c.unique) sql += " UNIQUE";
406
- if (c.references) {
407
- sql += ` REFERENCES ${quote(c.references.table)}(${quote(c.references.column)})`;
408
- if (c.references.onDelete) sql += ` ON DELETE ${c.references.onDelete.toUpperCase()}`;
409
- }
410
- return sql;
411
- }
412
- function createTableSql(dialect, def, schema) {
413
- const parts = def.columns.map((c) => columnSql(dialect, c));
414
- for (const ck of def.checks ?? []) parts.push(`CHECK (${ck.expression})`);
415
- for (const u of def.uniques ?? []) parts.push(`UNIQUE (${u.columns.map(quote).join(", ")})`);
416
- const strict = dialect === "sqlite" ? " STRICT" : "";
417
- return `CREATE TABLE IF NOT EXISTS ${schema === void 0 || schema === "public" ? quote(def.name) : `${quote(schema)}.${quote(def.name)}`} (\n ${parts.join(",\n ")}\n)${strict}`;
418
- }
419
- function createIndexSql(def, name, schema) {
420
- const idx = def.indexes?.find((i) => i.name === name);
421
- if (idx === void 0) throw new Error(`unknown index "${name}" on table "${def.name}"`);
422
- const qualified = schema === void 0 || schema === "public" ? quote(def.name) : `${quote(schema)}.${quote(def.name)}`;
423
- return `CREATE INDEX IF NOT EXISTS ${quote(idx.name)} ON ${qualified}(${idx.columns.map(quote).join(", ")})`;
424
- }
425
- function createTablesSql(dialect, defs, schema) {
426
- const statements = [];
427
- for (const def of defs) {
428
- statements.push(createTableSql(dialect, def, schema));
429
- for (const idx of def.indexes ?? []) statements.push(createIndexSql(def, idx.name, schema));
430
- }
431
- return statements;
432
- }
433
- //#endregion
434
- //#region src/entities/persistence-state.ts
435
- const persistenceState = {
436
- name: "t_persistence_state",
437
- columns: [{
438
- name: "f_singleton",
439
- type: "integer",
440
- primaryKey: true
441
- }, {
442
- name: "f_store_id",
443
- type: "text",
444
- notNull: true
445
- }],
446
- checks: [{
447
- name: "ck_persistence_state_singleton",
448
- expression: "f_singleton = 1"
449
- }]
450
- };
451
- //#endregion
452
- //#region src/entities/schema-meta.ts
453
- const schemaMeta = {
454
- name: "t_schema_meta",
455
- columns: [{
456
- name: "f_key",
457
- type: "text",
458
- primaryKey: true
459
- }, {
460
- name: "f_value",
461
- type: "text",
462
- notNull: true
463
- }]
464
- };
465
- //#endregion
466
- //#region src/entities/sessions.ts
467
- const sessions = {
468
- name: "t_sessions",
469
- columns: [
470
- {
471
- name: "f_id",
472
- type: "serial",
473
- primaryKey: true
474
- },
475
- {
476
- name: "f_session_id",
477
- type: "text",
478
- notNull: true,
479
- unique: true
480
- },
481
- {
482
- name: "f_head_event_id",
483
- type: "text",
484
- notNull: true,
485
- default: ""
486
- },
487
- {
488
- name: "f_head_sequence",
489
- type: "integer",
490
- notNull: true,
491
- default: -1
492
- },
493
- {
494
- name: "f_version",
495
- type: "integer",
496
- notNull: true
497
- },
498
- {
499
- name: "f_created_at",
500
- type: "bigint",
501
- notNull: true
502
- },
503
- {
504
- name: "f_cwd",
505
- type: "text"
506
- },
507
- {
508
- name: "f_parent_session",
509
- type: "text"
510
- },
511
- {
512
- name: "f_seed_length",
513
- type: "integer"
514
- },
515
- {
516
- name: "f_origin",
517
- type: "text"
518
- },
519
- {
520
- name: "f_delegation_depth",
521
- type: "integer"
522
- },
523
- {
524
- name: "f_incarnation",
525
- type: "text",
526
- notNull: true
527
- },
528
- {
529
- name: "f_revision",
530
- type: "integer",
531
- notNull: true
532
- }
533
- ]
534
- };
535
- //#endregion
536
- //#region src/entities/events.ts
537
- const events = {
538
- name: "t_events",
539
- columns: [
540
- {
541
- name: "f_id",
542
- type: "serial",
543
- primaryKey: true
544
- },
545
- {
546
- name: "f_event_id",
547
- type: "text",
548
- notNull: true,
549
- unique: true
550
- },
551
- {
552
- name: "f_parent_id",
553
- type: "text",
554
- notNull: true,
555
- default: ""
556
- },
557
- {
558
- name: "f_type",
559
- type: "text",
560
- notNull: true,
561
- default: ""
562
- },
563
- {
564
- name: "f_kind",
565
- type: "text",
566
- notNull: true,
567
- default: ""
568
- },
569
- {
570
- name: "f_role",
571
- type: "text",
572
- notNull: true,
573
- default: ""
574
- },
575
- {
576
- name: "f_name",
577
- type: "text",
578
- notNull: true,
579
- default: ""
580
- },
581
- {
582
- name: "f_action_id",
583
- type: "text",
584
- notNull: true,
585
- default: ""
586
- },
587
- {
588
- name: "f_encoding",
589
- type: "text",
590
- notNull: true,
591
- default: ""
592
- },
593
- {
594
- name: "f_data",
595
- type: "text",
596
- notNull: true
597
- },
598
- {
599
- name: "f_created_at",
600
- type: "bigint",
601
- notNull: true,
602
- default: 0
603
- }
604
- ],
605
- indexes: [
606
- {
607
- name: "idx_events_kind",
608
- columns: ["f_kind"]
609
- },
610
- {
611
- name: "idx_events_role",
612
- columns: ["f_role"]
613
- },
614
- {
615
- name: "idx_events_name",
616
- columns: ["f_name"]
617
- },
618
- {
619
- name: "idx_events_action_id",
620
- columns: ["f_action_id"]
621
- }
622
- ]
623
- };
624
- //#endregion
625
- //#region src/entities/session-events.ts
626
- const sessionEvents = {
627
- name: "t_session_events",
628
- columns: [
629
- {
630
- name: "f_id",
631
- type: "serial",
632
- primaryKey: true
633
- },
634
- {
635
- name: "f_session_id",
636
- type: "text",
637
- notNull: true,
638
- references: {
639
- table: "t_sessions",
640
- column: "f_session_id",
641
- onDelete: "cascade"
642
- }
643
- },
644
- {
645
- name: "f_event_id",
646
- type: "text",
647
- notNull: true,
648
- references: {
649
- table: "t_events",
650
- column: "f_event_id",
651
- onDelete: "cascade"
652
- }
653
- },
654
- {
655
- name: "f_sequence",
656
- type: "integer",
657
- notNull: true
658
- },
659
- {
660
- name: "f_original_seq",
661
- type: "integer",
662
- notNull: true
663
- },
664
- {
665
- name: "f_surface_op",
666
- type: "text"
667
- }
668
- ],
669
- uniques: [{
670
- name: "uq_session_events_session_sequence",
671
- columns: ["f_session_id", "f_sequence"]
672
- }],
673
- indexes: [{
674
- name: "idx_session_events_event_id",
675
- columns: ["f_event_id"]
676
- }]
677
- };
678
- //#endregion
679
- //#region src/entities/index.ts
680
- const sqliteTableDefs = [
681
- persistenceState,
682
- sessions,
683
- events,
684
- sessionEvents
685
- ];
686
- const postgresTableDefs = [
687
- persistenceState,
688
- schemaMeta,
689
- sessions,
690
- events,
691
- sessionEvents
692
- ];
693
- //#endregion
694
- //#region src/schema.ts
695
- const SCHEMA_VERSION = 2;
696
- const SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 1146308688;
697
- const EPHEMERAL_EVENT_TYPES = ["assistant/chunk"];
698
- const EVENT_ENCODING = "json";
699
- const sqliteTables = toSqliteSchema(sqliteTableDefs);
700
- const tPersistenceState = sqliteTables["t_persistence_state"];
701
- const tSessions = sqliteTables["t_sessions"];
702
- const tEvents = sqliteTables["t_events"];
703
- const tSessionEvents = sqliteTables["t_session_events"];
704
- const DEFAULT_BUSY_TIMEOUT_MS = 5e3;
705
- function isEphemeralType(type) {
706
- return EPHEMERAL_EVENT_TYPES.includes(type);
707
- }
708
- function isPersistedEvent(event) {
709
- return !isEphemeralType(event.type) && event.ignorable !== true;
710
- }
711
- function eventKind(event) {
712
- switch (event.type) {
713
- case "user/message": return "message";
714
- case "assistant/message":
715
- if ((event.data.message?.content)?.some((block) => block?.type === "reasoning")) return "thinking";
716
- return "message";
717
- case "turn/start":
718
- case "turn/end":
719
- case "step/start":
720
- case "step/end":
721
- case "session/end-seed": return "turn";
722
- case "tool/call":
723
- case "tool/result":
724
- case "tool/code-dispatch-start":
725
- case "tool/code-dispatch": return "tool";
726
- case "request/header":
727
- case "request/context": return "request";
728
- case "model/selection":
729
- case "permission/preset":
730
- case "approval/policy":
731
- case "sandbox/mode":
732
- case "plan/mode":
733
- case "agent-preset/selected": return "config";
734
- case "approval/asked":
735
- case "approval/decided":
736
- case "command/run":
737
- case "command/done":
738
- case "hook/invoked":
739
- case "hook/result":
740
- case "feedback/record": return "audit";
741
- case "session/title":
742
- case "session/title-llm-request":
743
- case "session-log-deepseek/delivery-accepted": return "lifecycle";
744
- case "agent/inbox/spliced": return "inbox";
745
- case "compaction/start":
746
- case "compaction/end":
747
- case "compaction/summary":
748
- case "compaction/prune": return "compaction";
749
- case "llm/retry":
750
- case "llm/retry-started": return "llm";
751
- case "subagent/descriptor":
752
- case "subagent/model-selection-policy": return "subagent";
753
- case "team/member":
754
- case "team/task":
755
- case "team/message/queued":
756
- case "team/message/delivered": return "team";
757
- case "tool-workflow/run-start":
758
- case "tool-workflow/run-end":
759
- case "tool-workflow/agent-start":
760
- case "tool-workflow/agent-end": return "workflow";
761
- case "goal/change": return "goal";
762
- case "schedule/change": return "schedule";
763
- case "todo/write": return "todo";
764
- case "web/deepseek-search-llm-request": return "web";
765
- default: return "";
766
- }
767
- }
768
- function eventDimensions(event) {
769
- const kind = eventKind(event);
770
- const type = event.type;
771
- const data = event.data;
772
- switch (type) {
773
- case "user/message": return {
774
- kind,
775
- role: "user",
776
- name: "",
777
- actionId: ""
778
- };
779
- case "assistant/message": return {
780
- kind,
781
- role: "assistant",
782
- name: "",
783
- actionId: ""
784
- };
785
- case "tool/result": return {
786
- kind,
787
- role: "tool",
788
- name: "",
789
- actionId: data["message"]?.content?.[0]?.toolCallId ?? ""
790
- };
791
- case "tool/call": return {
792
- kind,
793
- role: "",
794
- name: typeof data["name"] === "string" ? data["name"] : "",
795
- actionId: typeof data["callId"] === "string" ? data["callId"] : ""
796
- };
797
- case "tool/code-dispatch-start":
798
- case "tool/code-dispatch": return {
799
- kind,
800
- role: "",
801
- name: "",
802
- actionId: typeof data["subCallId"] === "string" ? data["subCallId"] : ""
803
- };
804
- case "command/run":
805
- case "command/done": return {
806
- kind,
807
- role: "",
808
- name: type === "command/run" && typeof data["name"] === "string" ? data["name"] : "",
809
- actionId: typeof data["commandId"] === "string" ? data["commandId"] : ""
810
- };
811
- case "approval/asked":
812
- case "approval/decided": return {
813
- kind,
814
- role: "",
815
- name: "",
816
- actionId: typeof data["id"] === "string" ? data["id"] : ""
817
- };
818
- case "hook/invoked":
819
- case "hook/result": return {
820
- kind,
821
- role: "",
822
- name: "",
823
- actionId: typeof data["handlerId"] === "string" ? data["handlerId"] : ""
824
- };
825
- case "llm/retry":
826
- case "llm/retry-started": return {
827
- kind,
828
- role: "",
829
- name: "",
830
- actionId: typeof data["retryId"] === "string" ? data["retryId"] : ""
831
- };
832
- case "tool-workflow/run-start":
833
- case "tool-workflow/run-end":
834
- case "tool-workflow/agent-start":
835
- case "tool-workflow/agent-end": return {
836
- kind,
837
- role: "",
838
- name: "",
839
- actionId: typeof data["runId"] === "string" ? data["runId"] : ""
840
- };
841
- case "todo/write": return {
842
- kind,
843
- role: "",
844
- name: "todos",
845
- actionId: ""
846
- };
847
- case "subagent/descriptor": return {
848
- kind,
849
- role: "",
850
- name: typeof data["label"] === "string" ? data["label"] : "",
851
- actionId: ""
852
- };
853
- default: return {
854
- kind,
855
- role: "",
856
- name: "",
857
- actionId: ""
858
- };
859
- }
860
- }
861
- function migrateSqliteV1ToV2(db) {
862
- const { user_version: onDisk } = db.prepare("PRAGMA user_version").get();
863
- if (onDisk === 2) return 0;
864
- if (onDisk !== 1) throw new Error(`unsupported schema version ${onDisk} (expected 1)`);
865
- db.exec(`
866
- CREATE TABLE t_events_v2 (
867
- f_id INTEGER PRIMARY KEY AUTOINCREMENT,
868
- f_event_id TEXT NOT NULL UNIQUE,
869
- f_parent_id TEXT NOT NULL DEFAULT '',
870
- f_type TEXT NOT NULL DEFAULT '',
871
- f_kind TEXT NOT NULL DEFAULT '',
872
- f_role TEXT NOT NULL DEFAULT '',
873
- f_name TEXT NOT NULL DEFAULT '',
874
- f_action_id TEXT NOT NULL DEFAULT '',
875
- f_encoding TEXT NOT NULL DEFAULT '',
876
- f_data TEXT NOT NULL,
877
- f_created_at INTEGER NOT NULL DEFAULT 0
878
- ) STRICT;
879
- CREATE TABLE t_session_events_v2 (
880
- f_id INTEGER PRIMARY KEY AUTOINCREMENT,
881
- f_session_id TEXT NOT NULL REFERENCES t_sessions(f_session_id) ON DELETE CASCADE,
882
- f_event_id TEXT NOT NULL REFERENCES t_events_v2(f_event_id) ON DELETE CASCADE,
883
- f_sequence INTEGER NOT NULL,
884
- f_original_seq INTEGER NOT NULL,
885
- f_surface_op TEXT,
886
- UNIQUE (f_session_id, f_sequence)
887
- ) STRICT;
888
- CREATE INDEX idx_events_v2_kind ON t_events_v2(f_kind);
889
- CREATE INDEX idx_events_v2_role ON t_events_v2(f_role);
890
- CREATE INDEX idx_events_v2_name ON t_events_v2(f_name);
891
- CREATE INDEX idx_events_v2_action_id ON t_events_v2(f_action_id);
892
- CREATE INDEX idx_session_events_v2_event_id ON t_session_events_v2(f_event_id);
893
- `);
894
- const oldEvents = db.prepare(`SELECT f_event_id, f_parent_id, f_kind, f_data, f_created_at FROM t_events ORDER BY f_id`).all();
895
- const insertEvent = db.prepare(`
896
- INSERT INTO t_events_v2
897
- (f_event_id, f_parent_id, f_type, f_kind, f_role, f_name, f_action_id, f_encoding, f_data, f_created_at)
898
- VALUES (?, ?, ?, ?, ?, ?, ?, 'json', ?, ?)
899
- `);
900
- for (const row of oldEvents) {
901
- let data;
902
- try {
903
- data = JSON.parse(row.f_data);
904
- } catch {
905
- throw new Error(`t_events row ${row.f_event_id} has unparsable f_data; aborting`);
906
- }
907
- const dims = eventDimensions({
908
- type: row.f_kind,
909
- seq: 0,
910
- time: row.f_created_at,
911
- data
912
- });
913
- insertEvent.run(row.f_event_id, row.f_parent_id, row.f_kind, dims.kind, dims.role, dims.name, dims.actionId, row.f_data, row.f_created_at);
914
- }
915
- const oldBridges = db.prepare(`SELECT se.f_session_id, se.f_event_id, se.f_sequence
916
- FROM t_session_events se ORDER BY se.f_id`).all();
917
- const oldEventMeta = new Map(db.prepare(`SELECT f_event_id, f_original_seq, f_surface_op FROM t_events`).all().map((row) => [row.f_event_id, row]));
918
- const insertBridge = db.prepare(`
919
- INSERT INTO t_session_events_v2
920
- (f_session_id, f_event_id, f_sequence, f_original_seq, f_surface_op)
921
- VALUES (?, ?, ?, ?, ?)
922
- `);
923
- for (const row of oldBridges) {
924
- const meta = oldEventMeta.get(row.f_event_id);
925
- if (meta === void 0) throw new Error(`bridge row ${row.f_session_id}:${row.f_sequence} references missing event ${row.f_event_id}`);
926
- insertBridge.run(row.f_session_id, row.f_event_id, row.f_sequence, meta.f_original_seq, meta.f_surface_op);
927
- }
928
- db.exec(`
929
- DROP TABLE t_session_events;
930
- DROP TABLE t_events;
931
- ALTER TABLE t_events_v2 RENAME TO t_events;
932
- ALTER TABLE t_session_events_v2 RENAME TO t_session_events;
933
- `);
934
- db.exec(`PRAGMA user_version = 2`);
935
- return oldEvents.length;
936
- }
937
- //#endregion
938
- //#region src/sqlite.ts
939
- const sqliteTxQueues = /* @__PURE__ */ new Map();
940
- function enqueueSqliteTx(path, fn) {
941
- const run = (sqliteTxQueues.get(path) ?? Promise.resolve()).then(fn);
942
- sqliteTxQueues.set(path, run.then(() => void 0, () => void 0));
943
- return run;
944
- }
945
- async function createDatabaseFile(path) {
946
- try {
947
- await (await open(path, "wx", 384)).close();
948
- } catch (error) {
949
- if (error.code !== "EEXIST") throw error;
950
- }
951
- }
952
- function openDatabase(path, journalMode, busyTimeout = DEFAULT_BUSY_TIMEOUT_MS) {
953
- const db = new DatabaseSync(path);
954
- try {
955
- configureDatabase(db, path, journalMode, busyTimeout);
956
- return db;
957
- } catch (error) {
958
- db.close();
959
- throw error;
960
- }
961
- }
962
- function configureDatabase(db, path, journalMode, busyTimeout) {
963
- db.exec("PRAGMA foreign_keys = ON");
964
- db.exec(`PRAGMA busy_timeout = ${busyTimeout}`);
965
- drizzle$1({ client: db }).transaction((tx) => {
966
- const { user_version: onDisk } = tx.get(sql`PRAGMA user_version`);
967
- const { application_id: applicationId } = tx.get(sql`PRAGMA application_id`);
968
- const { count: userObjectCount } = tx.get(sql`SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*'`);
969
- if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) throw new Error(`session database at "${path}" has an unversioned schema or application identity`);
970
- if (onDisk === 1) migrateSqliteV1ToV2(db);
971
- else if (onDisk !== 0 && onDisk !== 2) throw new Error(`session database at "${path}" has schema version ${onDisk}, incompatible with this build (2)`);
972
- if ((onDisk === 2 || onDisk === 1) && applicationId !== 1146308688) throw new Error(`session database at "${path}" has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`);
973
- for (const statement of createTablesSql("sqlite", sqliteTableDefs)) tx.run(sql.raw(statement));
974
- tx.insert(tPersistenceState).values({
975
- fSingleton: 1,
976
- fStoreId: randomUUID()
977
- }).onConflictDoNothing().run();
978
- if (onDisk === 0) {
979
- tx.run(sql.raw(`PRAGMA application_id = ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`));
980
- tx.run(sql.raw(`PRAGMA user_version = 2`));
981
- }
982
- }, { behavior: "immediate" });
983
- db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);
984
- }
985
- var SqliteBackend = class SqliteBackend {
986
- options;
987
- kind = "sqlite";
988
- storeIdentity;
989
- dbPath = "";
990
- db;
991
- constructor(options) {
992
- this.options = options;
993
- }
994
- async open() {
995
- const actual = this.options.path === ":memory:" ? this.options.path : resolve(this.options.path);
996
- this.dbPath = actual;
997
- if (actual !== ":memory:") {
998
- await mkdir(dirname(actual), {
999
- recursive: true,
1000
- mode: 448
1001
- });
1002
- await createDatabaseFile(actual);
1003
- }
1004
- await enqueueSqliteTx(actual, async () => {
1005
- this.db = drizzle$1({ client: openDatabase(actual, this.options.journalMode, this.options.busyTimeout) });
1006
- });
1007
- try {
1008
- const row = this.db.select({ fStoreId: tPersistenceState.fStoreId }).from(tPersistenceState).where(eq(tPersistenceState.fSingleton, 1)).get();
1009
- /* v8 ignore next -- openDatabase inserts the singleton before returning. */
1010
- if (row === void 0) throw new Error(`session database at "${actual}" has no store identity`);
1011
- if (row.fStoreId.length === 0) throw new Error(`session database at "${actual}" has no valid store identity`);
1012
- if (actual !== ":memory:") {
1013
- const identity = statSync(actual, { bigint: true });
1014
- this.storeIdentity = `file:${identity.dev}:${identity.ino}:${identity.birthtimeNs}:store:${row.fStoreId}`;
1015
- } else this.storeIdentity = `memory:store:${row.fStoreId}`;
1016
- } catch (error) {
1017
- this.db.$client.close();
1018
- throw error;
1019
- }
1020
- }
1021
- async close() {
1022
- if (this.db === void 0) return;
1023
- this.db.$client.close();
1024
- }
1025
- async getSession(id) {
1026
- return this.db.select().from(tSessions).where(eq(tSessions.fSessionId, id)).get();
1027
- }
1028
- async getSeqMapRows(id) {
1029
- return this.db.select({
1030
- fSequence: tSessionEvents.fSequence,
1031
- fOriginalSeq: tSessionEvents.fOriginalSeq
1032
- }).from(tSessionEvents).where(eq(tSessionEvents.fSessionId, id)).all();
1033
- }
1034
- async getEventRows(id, fromSequence) {
1035
- return (fromSequence === void 0 ? this.eventRows().where(eq(tSessionEvents.fSessionId, id)) : this.eventRows().where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)))).orderBy(tSessionEvents.fSequence).all();
1036
- }
1037
- async listSessions() {
1038
- return this.db.select().from(tSessions).all();
1039
- }
1040
- async transaction(fn) {
1041
- return enqueueSqliteTx(this.dbPath, async () => {
1042
- this.db.$client.exec("BEGIN IMMEDIATE");
1043
- try {
1044
- const result = await fn(this.tx);
1045
- this.db.$client.exec("COMMIT");
1046
- return result;
1047
- } catch (error) {
1048
- /* v8 ignore start */
1049
- try {
1050
- this.db.$client.exec("ROLLBACK");
1051
- } catch {}
1052
- throw error;
1053
- }
1054
- });
1055
- }
1056
- tx = {
1057
- upsertSession: (storage, incarnation) => this.upsertSession(storage, incarnation),
1058
- getHead: (id) => this.getHead(id),
1059
- getSeedLength: (id) => this.getSeedLength(id),
1060
- updateSeedLength: (id, seedLength) => this.updateSeedLength(id, seedLength),
1061
- insertEvents: (events) => this.insertEvents(events),
1062
- insertBridges: (rows) => this.insertBridges(rows),
1063
- updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),
1064
- bumpRevision: (id) => this.bumpRevision(id),
1065
- deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),
1066
- getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),
1067
- getLastBridge: (id) => this.getLastBridge(id)
1068
- };
1069
- async upsertSession(storage, incarnation) {
1070
- this.db.insert(tSessions).values(sessionInsertRow(storage, incarnation)).onConflictDoUpdate({
1071
- target: tSessions.fSessionId,
1072
- set: sessionConflictRow(storage)
1073
- }).run();
1074
- }
1075
- async getHead(id) {
1076
- const head = this.db.select({
1077
- fHeadEventId: tSessions.fHeadEventId,
1078
- fHeadSequence: tSessions.fHeadSequence
1079
- }).from(tSessions).where(eq(tSessions.fSessionId, id)).get();
1080
- /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */
1081
- if (head === void 0) throw new Error(`session "${id}" has no materialized row`);
1082
- return head;
1083
- }
1084
- async getSeedLength(id) {
1085
- const row = this.db.select({ fSeedLength: tSessions.fSeedLength }).from(tSessions).where(eq(tSessions.fSessionId, id)).get();
1086
- /* v8 ignore next -- rewind always materializes the row before reading the seed length */
1087
- if (row === void 0) throw new Error(`session "${id}" has no materialized row`);
1088
- return row.fSeedLength;
1089
- }
1090
- async updateSeedLength(id, seedLength) {
1091
- this.db.update(tSessions).set({ fSeedLength: seedLength }).where(eq(tSessions.fSessionId, id)).run();
1092
- }
1093
- static INSERT_BATCH_ROWS = 1e3;
1094
- async insertEvents(events) {
1095
- if (events.length === 0) return;
1096
- for (let i = 0; i < events.length; i += SqliteBackend.INSERT_BATCH_ROWS) this.db.insert(tEvents).values(events.slice(i, i + SqliteBackend.INSERT_BATCH_ROWS).map((event) => ({ ...event }))).run();
1097
- }
1098
- async insertBridges(rows) {
1099
- if (rows.length === 0) return;
1100
- for (let i = 0; i < rows.length; i += SqliteBackend.INSERT_BATCH_ROWS) this.db.insert(tSessionEvents).values(rows.slice(i, i + SqliteBackend.INSERT_BATCH_ROWS).map((row) => ({ ...row }))).run();
1101
- }
1102
- async updateHead(id, headEventId, headSequence) {
1103
- this.db.update(tSessions).set({
1104
- fHeadEventId: headEventId,
1105
- fHeadSequence: headSequence
1106
- }).where(eq(tSessions.fSessionId, id)).run();
1107
- }
1108
- async bumpRevision(id) {
1109
- this.db.update(tSessions).set({ fRevision: sql`${tSessions.fRevision} + 1` }).where(eq(tSessions.fSessionId, id)).run();
1110
- }
1111
- async deleteBridgeTail(id, fromSequence) {
1112
- this.db.delete(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence))).run();
1113
- }
1114
- async getPrevBridge(id, sequence) {
1115
- return this.db.select({
1116
- fEventId: tSessionEvents.fEventId,
1117
- fSequence: tSessionEvents.fSequence
1118
- }).from(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence))).get();
1119
- }
1120
- async getLastBridge(id) {
1121
- return this.db.select({
1122
- fEventId: tSessionEvents.fEventId,
1123
- fSequence: tSessionEvents.fSequence
1124
- }).from(tSessionEvents).where(eq(tSessionEvents.fSessionId, id)).orderBy(desc(tSessionEvents.fSequence)).limit(1).get();
1125
- }
1126
- eventRows() {
1127
- return this.db.select({
1128
- fEventId: tSessionEvents.fEventId,
1129
- fSequence: tSessionEvents.fSequence,
1130
- fOriginalSeq: tSessionEvents.fOriginalSeq,
1131
- fType: tEvents.fType,
1132
- fKind: tEvents.fKind,
1133
- fRole: tEvents.fRole,
1134
- fName: tEvents.fName,
1135
- fActionId: tEvents.fActionId,
1136
- fCreatedAt: tEvents.fCreatedAt,
1137
- fData: tEvents.fData,
1138
- fSurfaceOp: tSessionEvents.fSurfaceOp
1139
- }).from(tSessionEvents).innerJoin(tEvents, eq(tSessionEvents.fEventId, tEvents.fEventId));
1140
- }
1141
- };
1142
- //#endregion
1143
11
  //#region src/postgres.ts
1144
12
  var PostgresBackend = class PostgresBackend {
1145
13
  db;
@@ -1293,438 +161,6 @@ var PostgresBackend = class PostgresBackend {
1293
161
  }
1294
162
  };
1295
163
  //#endregion
1296
- //#region src/branch.ts
1297
- function locateTurnEnd(events, atSeq, mode = "after") {
1298
- const ends = events.filter((event) => event.type === "turn/end").map((event) => event.seq);
1299
- if (atSeq === void 0) {
1300
- const last = ends.at(-1);
1301
- if (last === void 0) throw new SessionBranchError("session has no closed turn", "OPEN_TURN");
1302
- return last;
1303
- }
1304
- if (mode === "before") {
1305
- let boundary = -1;
1306
- for (const seq of ends) if (seq < atSeq) boundary = seq;
1307
- else break;
1308
- return boundary;
1309
- }
1310
- const firstAfter = ends.find((seq) => seq >= atSeq);
1311
- if (firstAfter !== void 0) return firstAfter;
1312
- const lastStart = [...events].reverse().find((event) => event.type === "turn/start");
1313
- if (lastStart !== void 0 && lastStart.seq <= atSeq) throw new SessionBranchError(`anchor ${atSeq} lies inside an open turn`, "OPEN_TURN");
1314
- const last = ends.at(-1);
1315
- if (last === void 0) throw new SessionBranchError("session has no closed turn", "OPEN_TURN");
1316
- return last;
1317
- }
1318
- function renumber(events, offset) {
1319
- return events.map((event, index) => ({
1320
- ...event,
1321
- seq: offset + index
1322
- }));
1323
- }
1324
- function mintSessionId() {
1325
- return `session-${randomUUID()}`;
1326
- }
1327
- function truncateLiveSession(session, newLength) {
1328
- const s = session;
1329
- s.log.length = newLength;
1330
- s.eventsSnapshot = void 0;
1331
- s.headerFold = void 0;
1332
- s.headerFoldSeq = 0;
1333
- s.contextFold = void 0;
1334
- s.contextFoldSeq = 0;
1335
- s.derived = [];
1336
- s.derivedNodes = 0;
1337
- s.derivedGeneration = 0;
1338
- s.surfaceManager._state = {
1339
- nodes: [],
1340
- replaceGeneration: 0
1341
- };
1342
- s.surfaceManager._lastProcessedSeq = s.surfaceManager.baseSeq - 1;
1343
- s.surfaceManager._pendingPlan = void 0;
1344
- }
1345
- function replaceLiveSessionLog(session, events) {
1346
- const s = session;
1347
- s.log.length = 0;
1348
- s.log.push(...events);
1349
- s.eventsSnapshot = void 0;
1350
- s.headerFold = void 0;
1351
- s.headerFoldSeq = 0;
1352
- s.contextFold = void 0;
1353
- s.contextFoldSeq = 0;
1354
- s.derived = [];
1355
- s.derivedNodes = 0;
1356
- s.derivedGeneration = 0;
1357
- s.surfaceManager._state = {
1358
- nodes: [],
1359
- replaceGeneration: 0
1360
- };
1361
- s.surfaceManager._lastProcessedSeq = s.surfaceManager.baseSeq - 1;
1362
- s.surfaceManager._pendingPlan = void 0;
1363
- }
1364
- var SessionBranchRdbProvider = class {
1365
- persistence;
1366
- live;
1367
- name = "session-rdb";
1368
- constructor(persistence, live = {
1369
- getSession: () => void 0,
1370
- getAgent: () => void 0,
1371
- flush: async () => true,
1372
- setCoordinatorCursor: () => {},
1373
- setCoordinatorState: () => {},
1374
- setCoordinatorSeedLength: () => {}
1375
- }) {
1376
- this.persistence = persistence;
1377
- this.live = live;
1378
- }
1379
- async readBranchPrefix(id, atSeq, mode = "after", signal) {
1380
- const { events } = await this.readRawEvents(id, signal);
1381
- const boundary = locateTurnEnd(events, atSeq, mode);
1382
- return {
1383
- seq: boundary,
1384
- events: events.slice(0, boundary + 1)
1385
- };
1386
- }
1387
- async readRawEvents(id, signal) {
1388
- const stored = await this.persistence.loadStored(id, signal);
1389
- if (stored === void 0) throw new SessionBranchError(`session "${id}" not found`, "SESSION_NOT_FOUND");
1390
- return {
1391
- meta: stored.meta,
1392
- events: stored.events
1393
- };
1394
- }
1395
- async forkFrom(sourceId, options = {}, signal) {
1396
- signal?.throwIfAborted();
1397
- const { atSeq, anchorMode = "after", seedSuffix = [], childSessionId, meta = {} } = options;
1398
- const source = await this.persistence.inspect(sourceId, signal);
1399
- const boundary = locateTurnEnd(source.events, atSeq, anchorMode);
1400
- const prefix = source.events.slice(0, boundary + 1);
1401
- const childId = childSessionId ?? mintSessionId();
1402
- const childMeta = {
1403
- version: SESSION_FORMAT_VERSION,
1404
- id: childId,
1405
- createdAt: meta.createdAt ?? Date.now(),
1406
- ...meta.cwd !== void 0 ? { cwd: meta.cwd } : source.meta.cwd !== void 0 ? { cwd: source.meta.cwd } : {},
1407
- parentSession: sourceId,
1408
- isSeeded: true,
1409
- ...meta.agentPreset !== void 0 ? { agentPreset: meta.agentPreset } : source.meta.agentPreset !== void 0 ? { agentPreset: source.meta.agentPreset } : {},
1410
- ...meta.origin !== void 0 ? { origin: meta.origin } : {},
1411
- ...meta.delegationDepth !== void 0 ? { delegationDepth: meta.delegationDepth } : {}
1412
- };
1413
- const seed = [...renumber(prefix, 0), ...renumber(seedSuffix, prefix.length)];
1414
- const internals = this.persistence.internals();
1415
- const sourceRows = await internals.backend.getEventRows(sourceId);
1416
- const sourceEventIds = new Map(sourceRows.map((row) => [row.fOriginalSeq, row.fEventId]));
1417
- const reuse = /* @__PURE__ */ new Map();
1418
- for (const event of prefix) {
1419
- const eventId = sourceEventIds.get(event.seq);
1420
- if (eventId !== void 0) reuse.set(event.seq, eventId);
1421
- }
1422
- internals.registerReuseEventIds(childId, reuse);
1423
- await this.persistence.create(childMeta, prefix.length);
1424
- if (seed.length > 0) await this.persistence.append(childId, seed);
1425
- return childId;
1426
- }
1427
- async rewind(id, toBoundary, signal) {
1428
- signal?.throwIfAborted();
1429
- if (!Number.isSafeInteger(toBoundary) || toBoundary < -1) throw new SessionBranchError(`rewind boundary must be a non-negative safe integer, got ${toBoundary}`, "INVALID_BOUNDARY");
1430
- const live = this.live.getSession(id);
1431
- if (live !== void 0) await this.live.flush(live);
1432
- const raw = live === void 0 ? await this.readRawEvents(id, signal) : void 0;
1433
- const inspection = live === void 0 ? void 0 : await this.persistence.inspect(id, signal);
1434
- const events = live === void 0 ? raw.events : inspection.events;
1435
- const meta = live === void 0 ? raw.meta : inspection.meta;
1436
- const boundaryEvent = events[toBoundary];
1437
- if (toBoundary === -1) {} else if (boundaryEvent === void 0) throw new SessionBranchError(`rewind boundary ${toBoundary} does not exist in session "${id}"`, "INVALID_BOUNDARY");
1438
- else if (boundaryEvent.type !== "turn/end" && boundaryEvent.type !== "user/message") throw new SessionBranchError(`rewind boundary ${toBoundary} is not a turn/end or user/message (${boundaryEvent.type})`, "INVALID_BOUNDARY");
1439
- const rawKeepLength = toBoundary === -1 ? 0 : boundaryEvent.type === "turn/end" ? toBoundary + 1 : toBoundary;
1440
- const keepLength = balanceRewindPrefix(events.slice(0, rawKeepLength)).length;
1441
- const internals = this.persistence.internals();
1442
- const denseBoundary = live === void 0 ? keepLength - 1 : events.slice(0, keepLength).filter(isPersistedEvent).length - 1;
1443
- const newSeedLength = await internals.backend.transaction(async (tx) => {
1444
- signal?.throwIfAborted();
1445
- const head = await tx.getHead(id);
1446
- if (denseBoundary > head.fHeadSequence) throw new SessionBranchError(`rewind boundary ${toBoundary} is beyond the stored head ${head.fHeadSequence}`, "INVALID_BOUNDARY");
1447
- if (denseBoundary < head.fHeadSequence) {
1448
- await tx.deleteBridgeTail(id, denseBoundary + 1);
1449
- const prev = denseBoundary === -1 ? void 0 : await tx.getPrevBridge(id, denseBoundary);
1450
- if (prev === void 0) await tx.updateHead(id, "", -1);
1451
- else await tx.updateHead(id, prev.fEventId, prev.fSequence);
1452
- }
1453
- const storedSeedLength = await tx.getSeedLength(id);
1454
- let shrunk = storedSeedLength;
1455
- if (storedSeedLength !== null && storedSeedLength > denseBoundary + 1) {
1456
- await tx.updateSeedLength(id, denseBoundary + 1);
1457
- shrunk = denseBoundary + 1;
1458
- }
1459
- await tx.bumpRevision(id);
1460
- return shrunk;
1461
- });
1462
- if (newSeedLength !== null) this.live.setCoordinatorSeedLength(id, newSeedLength);
1463
- internals.writeGuard.confirmHead(id, denseBoundary);
1464
- if (live !== void 0) {
1465
- truncateLiveSession(live, keepLength);
1466
- const agent = this.live.getAgent(id);
1467
- if (agent !== void 0) {
1468
- agent.requestHeaderLogged = false;
1469
- const lastTurn = live.snapshotEvents().findLast((e) => e.type === "turn/start")?.data.turn ?? 0;
1470
- const phase = agent.phase;
1471
- if (phase !== void 0) phase.lastTurn = lastTurn;
1472
- }
1473
- this.live.setCoordinatorCursor(id, keepLength);
1474
- } else if (boundaryEvent?.type === "user/message") this.live.setCoordinatorState(id, keepLength, meta);
1475
- else await this.persistence.load(id);
1476
- const row = await internals.backend.getSession(id);
1477
- if (row === void 0) return {
1478
- header: {
1479
- version: SESSION_FORMAT_VERSION,
1480
- id,
1481
- createdAt: meta.createdAt,
1482
- ...meta.cwd !== void 0 ? { cwd: meta.cwd } : {},
1483
- ...meta.parentSession !== void 0 ? { parentSession: meta.parentSession } : {},
1484
- isSeeded: meta.isSeeded,
1485
- ...meta.origin !== void 0 ? { origin: meta.origin } : {},
1486
- ...meta.delegationDepth !== void 0 ? { delegationDepth: meta.delegationDepth } : {},
1487
- ...meta.agentPreset !== void 0 ? { agentPreset: meta.agentPreset } : {}
1488
- },
1489
- revision: await internals.readStoredRevision(id) ?? await this.persistence.readStoredRevision(id)
1490
- };
1491
- return {
1492
- header: rowToMeta(row),
1493
- revision: await internals.readStoredRevision(id)
1494
- };
1495
- }
1496
- };
1497
- var SessionBranchRdb = class extends SessionBranch {
1498
- static inject = ["sessionPersistence", "sessions"];
1499
- constructor(ctx) {
1500
- super(ctx);
1501
- }
1502
- provider = new SessionBranchRdbProvider(this.ctx.sessionPersistence, {
1503
- getSession: (id) => this.ctx.sessions.get(id),
1504
- getAgent: (id) => {
1505
- return this.ctx.get("agents")?.get(id);
1506
- },
1507
- flush: (session) => this.ctx.sessions.flush(session),
1508
- setCoordinatorCursor: (id, cursor) => {
1509
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(id);
1510
- if (state !== void 0) state.cursor = cursor;
1511
- },
1512
- setCoordinatorState: (id, cursor, meta) => {
1513
- const states = this.ctx.sessionPersistence.coordinator?.states;
1514
- if (states === void 0) return;
1515
- const state = states.get(id);
1516
- if (state !== void 0) state.cursor = cursor;
1517
- else states.set(id, {
1518
- meta,
1519
- cursor,
1520
- materialized: true
1521
- });
1522
- },
1523
- setCoordinatorSeedLength: (id, seedLength) => {
1524
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(id);
1525
- if (state?.storage !== void 0 && state.storage.inheritedEventCount > seedLength) state.storage = {
1526
- ...state.storage,
1527
- inheritedEventCount: seedLength
1528
- };
1529
- }
1530
- });
1531
- readBranchPrefix(id, atSeq, mode, signal) {
1532
- return this.provider.readBranchPrefix(id, atSeq, mode, signal);
1533
- }
1534
- readRawEvents(id, signal) {
1535
- return this.provider.readRawEvents(id, signal);
1536
- }
1537
- forkFrom(sourceId, options, signal) {
1538
- return this.provider.forkFrom(sourceId, options, signal);
1539
- }
1540
- rewind(id, toBoundary, signal) {
1541
- return this.provider.rewind(id, toBoundary, signal);
1542
- }
1543
- syncLiveCursor(sessionId) {
1544
- const live = this.ctx.sessions.get(sessionId);
1545
- if (live === void 0) return;
1546
- const state = this.ctx.sessionPersistence.coordinator?.states?.get(sessionId);
1547
- if (state === void 0) return;
1548
- let cursor = state.cursor;
1549
- while (live.snapshotEvents()[cursor]?.ignorable === true) cursor += 1;
1550
- state.cursor = cursor;
1551
- }
1552
- async timeline(sessionId, signal) {
1553
- const persistence = this.ctx.sessionPersistence;
1554
- const snapshots = await persistence.listSnapshots(signal);
1555
- const readOwnEvents = async (id, fromSeq, s) => {
1556
- const live = this.ctx.sessions.get(id);
1557
- if (live !== void 0) return live.snapshotEvents().slice(fromSeq);
1558
- return (await persistence.readFrom(id, fromSeq, s)).events;
1559
- };
1560
- return buildTimeline(snapshots, readOwnEvents, sessionId, signal);
1561
- }
1562
- };
1563
- //#endregion
1564
- //#region src/import.ts
1565
- const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
1566
- const SESSION_IMPORT_PATH = "/api/session.import";
1567
- const MAX_IMPORT_ZIP_BYTES = 67108864;
1568
- function expandProvenanceFromStorage(parsed) {
1569
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("imported session records must be objects");
1570
- const record = parsed;
1571
- if (record.sourceEventSeqs === void 0) return parsed;
1572
- if (!Number.isSafeInteger(record.seq) || record.seq < 0) throw new TypeError("imported session event seq must be a non-negative safe integer");
1573
- return {
1574
- ...record,
1575
- sourceEventSeqs: decodeSeqRanges(record.sourceEventSeqs, record.seq + 1)
1576
- };
1577
- }
1578
- function parseJsonlArtifact(content) {
1579
- const lines = content.split("\n");
1580
- if (lines.length === 0 || lines[0] === "") throw new Error("imported session log is empty");
1581
- let header;
1582
- try {
1583
- header = JSON.parse(lines[0]);
1584
- } catch {
1585
- throw new Error("imported session log has an unparsable header line");
1586
- }
1587
- 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");
1588
- const seedLength = header["seedLength"];
1589
- if (seedLength !== void 0 && (!Number.isSafeInteger(seedLength) || seedLength < 0)) throw new Error("imported session log has an invalid seedLength");
1590
- const events = [];
1591
- for (let i = 1; i < lines.length; i++) {
1592
- const line = lines[i];
1593
- if (line === void 0 || line === "") continue;
1594
- let parsed;
1595
- try {
1596
- parsed = JSON.parse(line);
1597
- } catch {
1598
- throw new Error(`imported session log has an unparsable event line at ${i}`);
1599
- }
1600
- for (const event of decodeStorageRecord(expandProvenanceFromStorage(parsed))) events.push(event);
1601
- }
1602
- 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`);
1603
- const origin = header["origin"];
1604
- const delegationDepth = header["delegationDepth"];
1605
- const inheritedEventCount = Math.min(seedLength ?? 0, events.length);
1606
- return {
1607
- meta: {
1608
- version: header["version"],
1609
- id: header["id"],
1610
- createdAt: header["createdAt"],
1611
- ...typeof header["cwd"] === "string" ? { cwd: header["cwd"] } : {},
1612
- ...typeof header["parentSession"] === "string" ? { parentSession: header["parentSession"] } : {},
1613
- isSeeded: seedLength !== void 0,
1614
- ...origin === "subagent" ? { origin } : {},
1615
- ...Number.isSafeInteger(delegationDepth) && delegationDepth > 0 ? { delegationDepth } : {},
1616
- ...typeof header["agentPreset"] === "string" ? { agentPreset: header["agentPreset"] } : {}
1617
- },
1618
- inheritedEventCount: SessionLogOffset(inheritedEventCount),
1619
- events
1620
- };
1621
- }
1622
- function parseImportZip(zip) {
1623
- let entries;
1624
- try {
1625
- entries = unzipSync(zip);
1626
- } catch {
1627
- throw new Error("imported zip is not a valid ZIP archive");
1628
- }
1629
- const artifact = entries[SESSION_LOG_ARTIFACT_FILENAME];
1630
- if (artifact === void 0) throw new Error(`imported zip is missing ${SESSION_LOG_ARTIFACT_FILENAME}`);
1631
- return parseJsonlArtifact(new TextDecoder().decode(artifact));
1632
- }
1633
- async function persistImport(persistence, branch, imported, targetId, sessions) {
1634
- const id = targetId ?? `session-${randomUUID()}`;
1635
- if (targetId !== void 0) {
1636
- if (branch === void 0) throw new Error("sessionBranch service is unavailable");
1637
- await branch.rewind(targetId, -1);
1638
- } else await persistence.create({
1639
- ...imported.meta,
1640
- id
1641
- }, imported.inheritedEventCount);
1642
- if (imported.events.length > 0) await persistence.append(id, imported.events);
1643
- if (targetId !== void 0) {
1644
- const live = sessions?.get(targetId);
1645
- if (live !== void 0) replaceLiveSessionLog(live, imported.events);
1646
- }
1647
- return id;
1648
- }
1649
- function registerSessionImport(ctx, persistence) {
1650
- ctx.inject(["webServer", "connection"], (webCtx) => {
1651
- const webServer = webCtx.webServer;
1652
- const connection = webCtx.get("connection");
1653
- return webCtx.effect(() => webServer.register({
1654
- kind: "exact",
1655
- path: SESSION_IMPORT_PATH,
1656
- handler: async (req, res) => {
1657
- const rejection = connection.requestRejection(req);
1658
- if (rejection !== void 0) {
1659
- res.writeHead(rejection);
1660
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
1661
- return;
1662
- }
1663
- const chunks = [];
1664
- for await (const chunk of req) chunks.push(chunk);
1665
- const body = Buffer.concat(chunks);
1666
- let envelope;
1667
- try {
1668
- envelope = JSON.parse(body.toString("utf8"));
1669
- } catch {
1670
- res.writeHead(400, { "content-type": "application/json" });
1671
- res.end(JSON.stringify({ error: "request body is not JSON" }));
1672
- return;
1673
- }
1674
- if (typeof envelope.zip !== "string" || envelope.zip === "") {
1675
- res.writeHead(400, { "content-type": "application/json" });
1676
- res.end(JSON.stringify({ error: "missing zip field" }));
1677
- return;
1678
- }
1679
- if (envelope.sessionId !== void 0 && (typeof envelope.sessionId !== "string" || envelope.sessionId === "")) {
1680
- res.writeHead(400, { "content-type": "application/json" });
1681
- res.end(JSON.stringify({ error: "sessionId must be a non-empty string" }));
1682
- return;
1683
- }
1684
- let zip;
1685
- try {
1686
- zip = Buffer.from(envelope.zip, "base64");
1687
- } catch {
1688
- res.writeHead(400, { "content-type": "application/json" });
1689
- res.end(JSON.stringify({ error: "zip field is not valid base64" }));
1690
- return;
1691
- }
1692
- if (zip.byteLength > MAX_IMPORT_ZIP_BYTES) {
1693
- res.writeHead(413, { "content-type": "application/json" });
1694
- res.end(JSON.stringify({ error: "imported zip exceeds the size limit" }));
1695
- return;
1696
- }
1697
- let imported;
1698
- try {
1699
- imported = parseImportZip(zip);
1700
- } catch (error) {
1701
- res.writeHead(400, { "content-type": "application/json" });
1702
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "imported zip is invalid" }));
1703
- return;
1704
- }
1705
- const targetId = typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
1706
- const branch = webCtx.get("sessionBranch");
1707
- try {
1708
- const sessions = webCtx.get("sessions");
1709
- const id = await persistImport(persistence, branch, imported, targetId, sessions);
1710
- res.writeHead(200, { "content-type": "application/json" });
1711
- res.end(JSON.stringify({ sessionId: id }));
1712
- } catch (error) {
1713
- const message = error instanceof Error ? error.message : String(error);
1714
- if (targetId !== void 0 && /not found/i.test(message)) {
1715
- res.writeHead(404, { "content-type": "application/json" });
1716
- res.end(JSON.stringify({ error: `session "${targetId}" not found` }));
1717
- return;
1718
- }
1719
- res.writeHead(500, { "content-type": "application/json" });
1720
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "import failed" }));
1721
- return;
1722
- }
1723
- }
1724
- }), `session-rdb: ${SESSION_IMPORT_PATH} route`);
1725
- });
1726
- }
1727
- //#endregion
1728
164
  //#region src/index.ts
1729
165
  var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersistence {
1730
166
  config;
@@ -1831,6 +267,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
1831
267
  }
1832
268
  this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
1833
269
  recomputeReplaceProvenance(log.events);
270
+ repairOrphanInboxSplices(log.events);
1834
271
  return {
1835
272
  meta: log.meta,
1836
273
  inheritedEventCount: SessionLogOffset(log.inheritedEventCount),
@@ -1861,7 +298,8 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
1861
298
  seqMap = buildSeqMap(eventRows);
1862
299
  } else {
1863
300
  eventRows = await this.backend.getEventRows(id, options.fromSeq);
1864
- seqMap = buildSeqMap(await this.backend.getSeqMapRows(id));
301
+ const seqRows = await this.backend.getSeqMapRows(id);
302
+ seqMap = buildSeqMap(seqRows);
1865
303
  }
1866
304
  signal?.throwIfAborted();
1867
305
  const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0, seqMap);
@@ -2026,5 +464,3 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
2026
464
  }
2027
465
  //#endregion
2028
466
  export { EPHEMERAL_EVENT_TYPES, SCHEMA_VERSION, SessionBranchRdb, SessionBranchRdbProvider, SessionPersistenceRdb, SessionPersistenceRdb as default, locateTurnEnd };
2029
-
2030
- //# sourceMappingURL=index.mjs.map