@handy_wote/pi-storage-sqlite-node 0.81.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 (52) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +5 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +81 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/sqlite/index.d.ts +5 -0
  8. package/dist/sqlite/index.d.ts.map +1 -0
  9. package/dist/sqlite/index.js +5 -0
  10. package/dist/sqlite/index.js.map +1 -0
  11. package/dist/sqlite/migrations/001_initial.sql +59 -0
  12. package/dist/sqlite/migrations.d.ts +9 -0
  13. package/dist/sqlite/migrations.d.ts.map +1 -0
  14. package/dist/sqlite/migrations.js +40 -0
  15. package/dist/sqlite/migrations.js.map +1 -0
  16. package/dist/sqlite/repo.d.ts +26 -0
  17. package/dist/sqlite/repo.d.ts.map +1 -0
  18. package/dist/sqlite/repo.js +158 -0
  19. package/dist/sqlite/repo.js.map +1 -0
  20. package/dist/sqlite/storage/branch-entries.d.ts +8 -0
  21. package/dist/sqlite/storage/branch-entries.d.ts.map +1 -0
  22. package/dist/sqlite/storage/branch-entries.js +43 -0
  23. package/dist/sqlite/storage/branch-entries.js.map +1 -0
  24. package/dist/sqlite/storage/index.d.ts +38 -0
  25. package/dist/sqlite/storage/index.d.ts.map +1 -0
  26. package/dist/sqlite/storage/index.js +347 -0
  27. package/dist/sqlite/storage/index.js.map +1 -0
  28. package/dist/sqlite/storage/session-entries.d.ts +17 -0
  29. package/dist/sqlite/storage/session-entries.d.ts.map +1 -0
  30. package/dist/sqlite/storage/session-entries.js +173 -0
  31. package/dist/sqlite/storage/session-entries.js.map +1 -0
  32. package/dist/sqlite/storage/session-materialized.d.ts +43 -0
  33. package/dist/sqlite/storage/session-materialized.d.ts.map +1 -0
  34. package/dist/sqlite/storage/session-materialized.js +282 -0
  35. package/dist/sqlite/storage/session-materialized.js.map +1 -0
  36. package/dist/sqlite/storage/session-sequences.d.ts +4 -0
  37. package/dist/sqlite/storage/session-sequences.d.ts.map +1 -0
  38. package/dist/sqlite/storage/session-sequences.js +14 -0
  39. package/dist/sqlite/storage/session-sequences.js.map +1 -0
  40. package/dist/sqlite/storage/sessions.d.ts +11 -0
  41. package/dist/sqlite/storage/sessions.d.ts.map +1 -0
  42. package/dist/sqlite/storage/sessions.js +27 -0
  43. package/dist/sqlite/storage/sessions.js.map +1 -0
  44. package/dist/sqlite/storage/shared.d.ts +10 -0
  45. package/dist/sqlite/storage/shared.d.ts.map +1 -0
  46. package/dist/sqlite/storage/shared.js +25 -0
  47. package/dist/sqlite/storage/shared.js.map +1 -0
  48. package/dist/sqlite/types.d.ts +46 -0
  49. package/dist/sqlite/types.d.ts.map +1 -0
  50. package/dist/sqlite/types.js +2 -0
  51. package/dist/sqlite/types.js.map +1 -0
  52. package/package.json +38 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,yBAAyB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGxH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AA2GzE,qBAAa,oBAAqB,YAAW,cAAc,CAAC,qBAAqB,CAAC;IACjF,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,iBAAiB,CAA2B;YAEtC,gCAAgC;YAqBhC,iBAAiB;YAqBjB,oBAAoB;IAkBlC,OAAO,eAeN;IAED,OAAa,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAUpG;IAED,OAAa,MAAM,CAClB,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC,GACC,OAAO,CAAC,oBAAoB,CAAC,CAiC/B;IAEK,WAAW,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAElD;IAEK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExC;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpD;IAEK,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CASrC;IAEK,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxD;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAgBhE;IAEK,WAAW,CAAC,KAAK,SAAS,gBAAgB,CAAC,MAAM,CAAC,EACvD,IAAI,EAAE,KAAK,GACT,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC,CAAC,CAiB5D;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEtD;IAEK,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAElD;IAEK,eAAe,8DAEpB;IAEK,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CASlF;IAEK,UAAU,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAkCjF;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7B;CACD","sourcesContent":["import type { LeafEntry, SessionEntryCursorOptions, SessionStorage, SessionTreeEntry } from \"@handy_wote/pi-agent-core\";\nimport { SessionError } from \"@handy_wote/pi-agent-core\";\nimport { uuidv7 } from \"@handy_wote/pi-ai\";\nimport type { SqliteDatabase, SqliteSessionMetadata } from \"../types.ts\";\nimport { getMaterializedBranchPathOrCompaction } from \"./branch-entries.ts\";\nimport { decodeEntry, encodeEntry, type SessionEntryRow } from \"./session-entries.ts\";\nimport {\n\tapplyEntryToMaterializedState,\n\tcreateEmptyMaterializedState,\n\ttype EntryMaterializedRow,\n\tentryMaterializedValues,\n\tmaterializedStateFromRows,\n\tmaterializedStateValues,\n\ttype SessionMaterializedRow,\n\ttype SessionMaterializedState,\n\tserializeSummary,\n\tsessionStatsFromMaterializedState,\n} from \"./session-materialized.ts\";\nimport { advanceSequence, getNextSequence } from \"./session-sequences.ts\";\nimport { rowToMetadata, type SessionRow } from \"./sessions.ts\";\nimport { generateEntryId, invalidSession, leafIdAfterEntry } from \"./shared.ts\";\n\nasync function decodeEntryRows(entryRows: SessionEntryRow[]): Promise<{\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst entries: SessionTreeEntry[] = [];\n\tlet leafId: string | null = null;\n\tfor (const entryRow of entryRows) {\n\t\ttry {\n\t\t\tconst entry = decodeEntry(entryRow);\n\t\t\tentries.push(entry);\n\t\t\tleafId = leafIdAfterEntry(entry);\n\t\t} catch {\n\t\t\t// Keep JSONL-like permissive resume behavior: skip malformed entries.\n\t\t}\n\t}\n\treturn { entries, leafId };\n}\n\nasync function loadEntryRowsByIds(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tentryIds: string[],\n): Promise<Map<string, SessionEntryRow>> {\n\tif (entryIds.length === 0) return new Map<string, SessionEntryRow>();\n\tconst placeholders = entryIds.map(() => \"?\").join(\", \");\n\tconst rows = await db\n\t\t.prepare(\n\t\t\t`SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id IN (${placeholders})`,\n\t\t)\n\t\t.all<SessionEntryRow>(sessionId, ...entryIds);\n\treturn new Map(rows.map((row) => [row.id, row]));\n}\n\nasync function loadActiveBranchId(db: SqliteDatabase, sessionId: string): Promise<string | null> {\n\t// branch_entries includes leaf navigation entries for the active branch, so the\n\t// newest branch_entries row identifies the branch that was most recently made active.\n\tconst row = await db\n\t\t.prepare(\n\t\t\t\"SELECT branch_id FROM branch_entries WHERE session_id = ? ORDER BY entry_seq DESC, branch_id DESC LIMIT 1\",\n\t\t)\n\t\t.get<{ branch_id: string }>(sessionId);\n\treturn row?.branch_id ?? null;\n}\n\nasync function hasExistingChild(db: SqliteDatabase, sessionId: string, parentId: string | null): Promise<boolean> {\n\tconst row =\n\t\tparentId === null\n\t\t\t? await db\n\t\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id IS NULL LIMIT 1\")\n\t\t\t\t\t.get<{ found: number }>(sessionId)\n\t\t\t: await db\n\t\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id = ? LIMIT 1\")\n\t\t\t\t\t.get<{ found: number }>(sessionId, parentId);\n\treturn row !== undefined;\n}\n\nasync function loadSqliteStorage(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n): Promise<{\n\trow: SessionRow;\n\tleafId: string | null;\n\tactiveBranchId: string | null;\n\tmaterializedState: SessionMaterializedState;\n}> {\n\tconst row = await db\n\t\t.prepare(\"SELECT id, created_at, metadata, cwd, parent_session_id, active_leaf_id FROM sessions WHERE id = ?\")\n\t\t.get<SessionRow>(sessionId);\n\tif (!row) throw new SessionError(\"not_found\", `Session not found: ${sessionId}`);\n\n\tconst leafId = row.active_leaf_id;\n\tconst materializedRow = await db\n\t\t.prepare(\"SELECT session_id, payload FROM session_materialized WHERE session_id = ?\")\n\t\t.get<SessionMaterializedRow>(sessionId);\n\tif (!materializedRow) throw invalidSession(`missing materialized row for session ${sessionId}`);\n\tconst entryMaterializedRows = await db\n\t\t.prepare(\n\t\t\t\"SELECT session_id, entry_seq, type, payload FROM entry_materialized WHERE session_id = ? ORDER BY entry_seq, type\",\n\t\t)\n\t\t.all<EntryMaterializedRow>(sessionId);\n\treturn {\n\t\trow,\n\t\tleafId,\n\t\tactiveBranchId: await loadActiveBranchId(db, sessionId),\n\t\tmaterializedState: materializedStateFromRows(materializedRow, entryMaterializedRows),\n\t};\n}\n\nexport class SqliteSessionStorage implements SessionStorage<SqliteSessionMetadata> {\n\tprivate readonly db: SqliteDatabase;\n\tprivate readonly metadata: SqliteSessionMetadata;\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate currentLeafId: string | null;\n\tprivate activeBranchId: string | null;\n\tprivate materializedState: SessionMaterializedState;\n\n\tprivate async getPathToRootOrCompactionEntries(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet stopAtEntryId: string | null = null;\n\t\tlet current = await this.getEntry(leafId);\n\t\tif (!current) throw new SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tif (stopAtEntryId !== null && current.id === stopAtEntryId) break;\n\t\t\tif (current.type === \"compaction\") {\n\t\t\t\tif (current.retainedTail) break;\n\t\t\t\tstopAtEntryId = current.firstKeptEntryId ?? null;\n\t\t\t}\n\t\t\tif (!current.parentId) break;\n\t\t\tconst parent = await this.getEntry(current.parentId);\n\t\t\tif (!parent) throw new SessionError(\"invalid_session\", `Entry ${current.parentId} not found`);\n\t\t\tcurrent = parent;\n\t\t}\n\t\treturn path;\n\t}\n\n\tprivate async materializeBranch(leafId: string | null): Promise<void> {\n\t\tconst branchId = uuidv7();\n\t\t// Rebuild the branch path only when branch membership changes: branch switch\n\t\t// (leaf navigation) or a new fork from a parent that already has a child.\n\t\t// Linear appends stay cheap and extend the active branch incrementally.\n\t\tconst path = await this.getPathToRootOrCompactionEntries(leafId);\n\t\tconst entryRowsById = await loadEntryRowsByIds(\n\t\t\tthis.db,\n\t\t\tthis.metadata.id,\n\t\t\tpath.map((entry) => entry.id),\n\t\t);\n\t\tfor (const entry of path) {\n\t\t\tconst entryRow = entryRowsById.get(entry.id);\n\t\t\tif (!entryRow) throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entry.id}`);\n\t\t\tawait this.db\n\t\t\t\t.prepare(\"INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)\")\n\t\t\t\t.run(this.metadata.id, branchId, entry.id, entryRow.entry_seq);\n\t\t}\n\t\tthis.activeBranchId = branchId;\n\t}\n\n\tprivate async appendToActiveBranch(entryId: string, parentId: string | null): Promise<void> {\n\t\tif (!this.activeBranchId) {\n\t\t\tawait this.materializeBranch(parentId);\n\t\t}\n\t\t// After a branch is materialized/resynced, subsequent linear appends only add the\n\t\t// new tip entry. We do not rebuild the full branch on every append.\n\t\tif (!this.activeBranchId) {\n\t\t\tthrow invalidSession(`active branch missing for session ${this.metadata.id}`);\n\t\t}\n\t\tconst entryRow = await this.db\n\t\t\t.prepare(\"SELECT entry_seq FROM session_entries WHERE session_id = ? AND id = ?\")\n\t\t\t.get<{ entry_seq: number }>(this.metadata.id, entryId);\n\t\tif (!entryRow) throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entryId}`);\n\t\tawait this.db\n\t\t\t.prepare(\"INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)\")\n\t\t\t.run(this.metadata.id, this.activeBranchId, entryId, entryRow.entry_seq);\n\t}\n\n\tprivate constructor(\n\t\tdb: SqliteDatabase,\n\t\tmetadata: SqliteSessionMetadata,\n\t\tentries: SessionTreeEntry[] | null,\n\t\tleafId: string | null,\n\t\tactiveBranchId: string | null,\n\t\tmaterializedState: SessionMaterializedState,\n\t) {\n\t\tthis.db = db;\n\t\tthis.metadata = metadata;\n\t\tthis.byId = new Map((entries ?? []).map((entry) => [entry.id, entry]));\n\t\tthis.materializedState = materializedState;\n\t\tthis.labelsById = materializedState.labelsById;\n\t\tthis.currentLeafId = leafId;\n\t\tthis.activeBranchId = activeBranchId;\n\t}\n\n\tstatic async open(db: SqliteDatabase, metadata: SqliteSessionMetadata): Promise<SqliteSessionStorage> {\n\t\tconst loaded = await loadSqliteStorage(db, metadata.id);\n\t\treturn new SqliteSessionStorage(\n\t\t\tdb,\n\t\t\trowToMetadata(loaded.row, metadata.path),\n\t\t\tnull,\n\t\t\tloaded.leafId,\n\t\t\tloaded.activeBranchId,\n\t\t\tloaded.materializedState,\n\t\t);\n\t}\n\n\tstatic async create(\n\t\tdb: SqliteDatabase,\n\t\tpath: string,\n\t\toptions: {\n\t\t\tcwd: string;\n\t\t\tsessionId: string;\n\t\t\tparentSessionId?: string;\n\t\t\tmetadata?: Record<string, unknown>;\n\t\t},\n\t): Promise<SqliteSessionStorage> {\n\t\tconst createdAt = new Date().toISOString();\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT INTO sessions (id, created_at, metadata, cwd, parent_session_id, active_leaf_id) VALUES (?, ?, ?, ?, ?, ?)\",\n\t\t\t)\n\t\t\t.run(\n\t\t\t\toptions.sessionId,\n\t\t\t\tcreatedAt,\n\t\t\t\toptions.metadata === undefined ? null : JSON.stringify(options.metadata),\n\t\t\t\toptions.cwd,\n\t\t\t\toptions.parentSessionId ?? null,\n\t\t\t\tnull,\n\t\t\t);\n\t\tawait db.prepare(\"INSERT INTO session_sequences (session_id, next_seq) VALUES (?, ?)\").run(options.sessionId, 1);\n\t\tawait db\n\t\t\t.prepare(\"INSERT INTO session_materialized (session_id, payload) VALUES (?, ?)\")\n\t\t\t.run(...materializedStateValues(options.sessionId, createEmptyMaterializedState()));\n\t\treturn new SqliteSessionStorage(\n\t\t\tdb,\n\t\t\t{\n\t\t\t\tid: options.sessionId,\n\t\t\t\tcreatedAt,\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tpath,\n\t\t\t\tparentSessionId: options.parentSessionId,\n\t\t\t\tmetadata: options.metadata,\n\t\t\t},\n\t\t\t[],\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tcreateEmptyMaterializedState(),\n\t\t);\n\t}\n\n\tasync getMetadata(): Promise<SqliteSessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.currentLeafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !(await this.getEntry(leafId))) {\n\t\t\tthrow new SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\t}\n\t\tconst entry: LeafEntry = {\n\t\t\ttype: \"leaf\",\n\t\t\tid: await this.createEntryId(),\n\t\t\tparentId: this.currentLeafId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId: leafId,\n\t\t};\n\t\tawait this.appendEntry(entry);\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\tfor (let i = 0; i < 100; i++) {\n\t\t\tconst id = generateEntryId(this.byId);\n\t\t\tconst existing = await this.db\n\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND id = ? LIMIT 1\")\n\t\t\t\t.get<{ found: number }>(this.metadata.id, id);\n\t\t\tif (!existing) return id;\n\t\t}\n\t\treturn uuidv7();\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tconst encoded = encodeEntry(entry);\n\t\tconst previousMaterializedState: SessionMaterializedState = {\n\t\t\t...this.materializedState,\n\t\t\tlabelsById: new Map(this.materializedState.labelsById),\n\t\t\tmodelThinkingConfigs: [...this.materializedState.modelThinkingConfigs],\n\t\t\tcurrentModel: this.materializedState.currentModel ? { ...this.materializedState.currentModel } : null,\n\t\t};\n\t\tconst previousById = new Map(this.byId);\n\t\tconst previousLeafId = this.currentLeafId;\n\t\tconst previousActiveBranchId = this.activeBranchId;\n\t\ttry {\n\t\t\tapplyEntryToMaterializedState(this.materializedState, entry);\n\t\t\tawait this.db.transaction(async () => {\n\t\t\t\tconst parentHadExistingChild = await hasExistingChild(this.db, this.metadata.id, entry.parentId);\n\t\t\t\tconst nextSeq = await getNextSequence(this.db, this.metadata.id);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT INTO session_entries (session_id, id, entry_seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.run(this.metadata.id, entry.id, nextSeq, entry.parentId, entry.type, entry.timestamp, encoded.payload);\n\t\t\t\tawait advanceSequence(this.db, this.metadata.id, nextSeq);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\"UPDATE session_materialized SET payload = ? WHERE session_id = ?\")\n\t\t\t\t\t.run(serializeSummary(this.materializedState), this.metadata.id);\n\t\t\t\tfor (const materializedEntry of entryMaterializedValues(entry)) {\n\t\t\t\t\tawait this.db\n\t\t\t\t\t\t.prepare(\"INSERT INTO entry_materialized (session_id, entry_seq, type, payload) VALUES (?, ?, ?, ?)\")\n\t\t\t\t\t\t.run(this.metadata.id, nextSeq, materializedEntry.type, materializedEntry.payload);\n\t\t\t\t}\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t\tthis.currentLeafId = leafIdAfterEntry(entry);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\"UPDATE sessions SET active_leaf_id = ? WHERE id = ?\")\n\t\t\t\t\t.run(this.currentLeafId, this.metadata.id);\n\t\t\t\tif (entry.type === \"leaf\") {\n\t\t\t\t\tthis.activeBranchId = null;\n\t\t\t\t\tawait this.materializeBranch(entry.targetId);\n\t\t\t\t\tawait this.appendToActiveBranch(entry.id, entry.parentId);\n\t\t\t\t} else {\n\t\t\t\t\tif (parentHadExistingChild) {\n\t\t\t\t\t\tawait this.materializeBranch(entry.parentId);\n\t\t\t\t\t}\n\t\t\t\t\tawait this.appendToActiveBranch(entry.id, entry.parentId);\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.materializedState = previousMaterializedState;\n\t\t\tthis.labelsById = previousMaterializedState.labelsById;\n\t\t\tthis.byId = previousById;\n\t\t\tthis.currentLeafId = previousLeafId;\n\t\t\tthis.activeBranchId = previousActiveBranchId;\n\t\t\tif (error instanceof SessionError) throw error;\n\t\t\tthrow new SessionError(\"storage\", `Failed to append SQLite session entry ${entry.id}`);\n\t\t}\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\tconst cached = this.byId.get(id);\n\t\tif (cached) return cached;\n\t\tconst row = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id = ?\",\n\t\t\t)\n\t\t\t.get<SessionEntryRow>(this.metadata.id, id);\n\t\tif (!row) return undefined;\n\t\ttry {\n\t\t\tconst entry = decodeEntry(row);\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\treturn entry;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\tconst rows = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND type = ? ORDER BY entry_seq\",\n\t\t\t)\n\t\t\t.all<SessionEntryRow>(this.metadata.id, type);\n\t\tconst entries: Array<Extract<SessionTreeEntry, { type: TType }>> = [];\n\t\tfor (const row of rows) {\n\t\t\ttry {\n\t\t\t\tconst entry = decodeEntry(row) as Extract<SessionTreeEntry, { type: TType }>;\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Keep JSONL-like permissive resume behavior: skip malformed entries.\n\t\t\t}\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\treturn this.materializedState.name;\n\t}\n\n\tasync getSessionStats() {\n\t\treturn sessionStatsFromMaterializedState(this.materializedState);\n\t}\n\n\tasync getPathToRootOrCompaction(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tif (leafId === this.currentLeafId) {\n\t\t\tif (!this.activeBranchId) {\n\t\t\t\tthrow invalidSession(`missing active branch for session ${this.metadata.id} leaf ${leafId}`);\n\t\t\t}\n\t\t\treturn getMaterializedBranchPathOrCompaction(this.db, this.metadata.id, this.activeBranchId, this.byId);\n\t\t}\n\t\treturn this.getPathToRootOrCompactionEntries(leafId);\n\t}\n\n\tasync getEntries(options?: SessionEntryCursorOptions): Promise<SessionTreeEntry[]> {\n\t\tconst limit = options?.limit;\n\t\tif (limit !== undefined) {\n\t\t\tconst beforeOrAtEntrySeq =\n\t\t\t\toptions?.afterEntrySeq ??\n\t\t\t\t(\n\t\t\t\t\tawait this.db\n\t\t\t\t\t\t.prepare(\"SELECT entry_seq FROM session_entries WHERE session_id = ? ORDER BY entry_seq DESC LIMIT 1\")\n\t\t\t\t\t\t.get<{ entry_seq: number }>(this.metadata.id)\n\t\t\t\t)?.entry_seq;\n\t\t\tif (beforeOrAtEntrySeq === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst rows = await this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND entry_seq <= ? ORDER BY entry_seq DESC LIMIT ?\",\n\t\t\t\t)\n\t\t\t\t.all<SessionEntryRow>(this.metadata.id, beforeOrAtEntrySeq, limit);\n\t\t\tconst entries = (await decodeEntryRows(rows)).entries;\n\t\t\tfor (const entry of entries) {\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t}\n\t\t\treturn entries.reverse();\n\t\t}\n\t\tconst rows = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? ORDER BY entry_seq\",\n\t\t\t)\n\t\t\t.all<SessionEntryRow>(this.metadata.id);\n\t\tconst entries = (await decodeEntryRows(rows)).entries;\n\t\tfor (const entry of entries) {\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync cleanup(): Promise<void> {\n\t\tawait this.db.close();\n\t}\n}\n"]}
@@ -0,0 +1,347 @@
1
+ import { SessionError } from "@handy_wote/pi-agent-core";
2
+ import { uuidv7 } from "@handy_wote/pi-ai";
3
+ import { getMaterializedBranchPathOrCompaction } from "./branch-entries.js";
4
+ import { decodeEntry, encodeEntry } from "./session-entries.js";
5
+ import { applyEntryToMaterializedState, createEmptyMaterializedState, entryMaterializedValues, materializedStateFromRows, materializedStateValues, serializeSummary, sessionStatsFromMaterializedState, } from "./session-materialized.js";
6
+ import { advanceSequence, getNextSequence } from "./session-sequences.js";
7
+ import { rowToMetadata } from "./sessions.js";
8
+ import { generateEntryId, invalidSession, leafIdAfterEntry } from "./shared.js";
9
+ async function decodeEntryRows(entryRows) {
10
+ const entries = [];
11
+ let leafId = null;
12
+ for (const entryRow of entryRows) {
13
+ try {
14
+ const entry = decodeEntry(entryRow);
15
+ entries.push(entry);
16
+ leafId = leafIdAfterEntry(entry);
17
+ }
18
+ catch {
19
+ // Keep JSONL-like permissive resume behavior: skip malformed entries.
20
+ }
21
+ }
22
+ return { entries, leafId };
23
+ }
24
+ async function loadEntryRowsByIds(db, sessionId, entryIds) {
25
+ if (entryIds.length === 0)
26
+ return new Map();
27
+ const placeholders = entryIds.map(() => "?").join(", ");
28
+ const rows = await db
29
+ .prepare(`SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id IN (${placeholders})`)
30
+ .all(sessionId, ...entryIds);
31
+ return new Map(rows.map((row) => [row.id, row]));
32
+ }
33
+ async function loadActiveBranchId(db, sessionId) {
34
+ // branch_entries includes leaf navigation entries for the active branch, so the
35
+ // newest branch_entries row identifies the branch that was most recently made active.
36
+ const row = await db
37
+ .prepare("SELECT branch_id FROM branch_entries WHERE session_id = ? ORDER BY entry_seq DESC, branch_id DESC LIMIT 1")
38
+ .get(sessionId);
39
+ return row?.branch_id ?? null;
40
+ }
41
+ async function hasExistingChild(db, sessionId, parentId) {
42
+ const row = parentId === null
43
+ ? await db
44
+ .prepare("SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id IS NULL LIMIT 1")
45
+ .get(sessionId)
46
+ : await db
47
+ .prepare("SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id = ? LIMIT 1")
48
+ .get(sessionId, parentId);
49
+ return row !== undefined;
50
+ }
51
+ async function loadSqliteStorage(db, sessionId) {
52
+ const row = await db
53
+ .prepare("SELECT id, created_at, metadata, cwd, parent_session_id, active_leaf_id FROM sessions WHERE id = ?")
54
+ .get(sessionId);
55
+ if (!row)
56
+ throw new SessionError("not_found", `Session not found: ${sessionId}`);
57
+ const leafId = row.active_leaf_id;
58
+ const materializedRow = await db
59
+ .prepare("SELECT session_id, payload FROM session_materialized WHERE session_id = ?")
60
+ .get(sessionId);
61
+ if (!materializedRow)
62
+ throw invalidSession(`missing materialized row for session ${sessionId}`);
63
+ const entryMaterializedRows = await db
64
+ .prepare("SELECT session_id, entry_seq, type, payload FROM entry_materialized WHERE session_id = ? ORDER BY entry_seq, type")
65
+ .all(sessionId);
66
+ return {
67
+ row,
68
+ leafId,
69
+ activeBranchId: await loadActiveBranchId(db, sessionId),
70
+ materializedState: materializedStateFromRows(materializedRow, entryMaterializedRows),
71
+ };
72
+ }
73
+ export class SqliteSessionStorage {
74
+ db;
75
+ metadata;
76
+ byId;
77
+ labelsById;
78
+ currentLeafId;
79
+ activeBranchId;
80
+ materializedState;
81
+ async getPathToRootOrCompactionEntries(leafId) {
82
+ if (leafId === null)
83
+ return [];
84
+ const path = [];
85
+ let stopAtEntryId = null;
86
+ let current = await this.getEntry(leafId);
87
+ if (!current)
88
+ throw new SessionError("not_found", `Entry ${leafId} not found`);
89
+ while (current) {
90
+ path.unshift(current);
91
+ if (stopAtEntryId !== null && current.id === stopAtEntryId)
92
+ break;
93
+ if (current.type === "compaction") {
94
+ if (current.retainedTail)
95
+ break;
96
+ stopAtEntryId = current.firstKeptEntryId ?? null;
97
+ }
98
+ if (!current.parentId)
99
+ break;
100
+ const parent = await this.getEntry(current.parentId);
101
+ if (!parent)
102
+ throw new SessionError("invalid_session", `Entry ${current.parentId} not found`);
103
+ current = parent;
104
+ }
105
+ return path;
106
+ }
107
+ async materializeBranch(leafId) {
108
+ const branchId = uuidv7();
109
+ // Rebuild the branch path only when branch membership changes: branch switch
110
+ // (leaf navigation) or a new fork from a parent that already has a child.
111
+ // Linear appends stay cheap and extend the active branch incrementally.
112
+ const path = await this.getPathToRootOrCompactionEntries(leafId);
113
+ const entryRowsById = await loadEntryRowsByIds(this.db, this.metadata.id, path.map((entry) => entry.id));
114
+ for (const entry of path) {
115
+ const entryRow = entryRowsById.get(entry.id);
116
+ if (!entryRow)
117
+ throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entry.id}`);
118
+ await this.db
119
+ .prepare("INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)")
120
+ .run(this.metadata.id, branchId, entry.id, entryRow.entry_seq);
121
+ }
122
+ this.activeBranchId = branchId;
123
+ }
124
+ async appendToActiveBranch(entryId, parentId) {
125
+ if (!this.activeBranchId) {
126
+ await this.materializeBranch(parentId);
127
+ }
128
+ // After a branch is materialized/resynced, subsequent linear appends only add the
129
+ // new tip entry. We do not rebuild the full branch on every append.
130
+ if (!this.activeBranchId) {
131
+ throw invalidSession(`active branch missing for session ${this.metadata.id}`);
132
+ }
133
+ const entryRow = await this.db
134
+ .prepare("SELECT entry_seq FROM session_entries WHERE session_id = ? AND id = ?")
135
+ .get(this.metadata.id, entryId);
136
+ if (!entryRow)
137
+ throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entryId}`);
138
+ await this.db
139
+ .prepare("INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)")
140
+ .run(this.metadata.id, this.activeBranchId, entryId, entryRow.entry_seq);
141
+ }
142
+ constructor(db, metadata, entries, leafId, activeBranchId, materializedState) {
143
+ this.db = db;
144
+ this.metadata = metadata;
145
+ this.byId = new Map((entries ?? []).map((entry) => [entry.id, entry]));
146
+ this.materializedState = materializedState;
147
+ this.labelsById = materializedState.labelsById;
148
+ this.currentLeafId = leafId;
149
+ this.activeBranchId = activeBranchId;
150
+ }
151
+ static async open(db, metadata) {
152
+ const loaded = await loadSqliteStorage(db, metadata.id);
153
+ return new SqliteSessionStorage(db, rowToMetadata(loaded.row, metadata.path), null, loaded.leafId, loaded.activeBranchId, loaded.materializedState);
154
+ }
155
+ static async create(db, path, options) {
156
+ const createdAt = new Date().toISOString();
157
+ await db
158
+ .prepare("INSERT INTO sessions (id, created_at, metadata, cwd, parent_session_id, active_leaf_id) VALUES (?, ?, ?, ?, ?, ?)")
159
+ .run(options.sessionId, createdAt, options.metadata === undefined ? null : JSON.stringify(options.metadata), options.cwd, options.parentSessionId ?? null, null);
160
+ await db.prepare("INSERT INTO session_sequences (session_id, next_seq) VALUES (?, ?)").run(options.sessionId, 1);
161
+ await db
162
+ .prepare("INSERT INTO session_materialized (session_id, payload) VALUES (?, ?)")
163
+ .run(...materializedStateValues(options.sessionId, createEmptyMaterializedState()));
164
+ return new SqliteSessionStorage(db, {
165
+ id: options.sessionId,
166
+ createdAt,
167
+ cwd: options.cwd,
168
+ path,
169
+ parentSessionId: options.parentSessionId,
170
+ metadata: options.metadata,
171
+ }, [], null, null, createEmptyMaterializedState());
172
+ }
173
+ async getMetadata() {
174
+ return this.metadata;
175
+ }
176
+ async getLeafId() {
177
+ return this.currentLeafId;
178
+ }
179
+ async setLeafId(leafId) {
180
+ if (leafId !== null && !(await this.getEntry(leafId))) {
181
+ throw new SessionError("not_found", `Entry ${leafId} not found`);
182
+ }
183
+ const entry = {
184
+ type: "leaf",
185
+ id: await this.createEntryId(),
186
+ parentId: this.currentLeafId,
187
+ timestamp: new Date().toISOString(),
188
+ targetId: leafId,
189
+ };
190
+ await this.appendEntry(entry);
191
+ }
192
+ async createEntryId() {
193
+ for (let i = 0; i < 100; i++) {
194
+ const id = generateEntryId(this.byId);
195
+ const existing = await this.db
196
+ .prepare("SELECT 1 AS found FROM session_entries WHERE session_id = ? AND id = ? LIMIT 1")
197
+ .get(this.metadata.id, id);
198
+ if (!existing)
199
+ return id;
200
+ }
201
+ return uuidv7();
202
+ }
203
+ async appendEntry(entry) {
204
+ const encoded = encodeEntry(entry);
205
+ const previousMaterializedState = {
206
+ ...this.materializedState,
207
+ labelsById: new Map(this.materializedState.labelsById),
208
+ modelThinkingConfigs: [...this.materializedState.modelThinkingConfigs],
209
+ currentModel: this.materializedState.currentModel ? { ...this.materializedState.currentModel } : null,
210
+ };
211
+ const previousById = new Map(this.byId);
212
+ const previousLeafId = this.currentLeafId;
213
+ const previousActiveBranchId = this.activeBranchId;
214
+ try {
215
+ applyEntryToMaterializedState(this.materializedState, entry);
216
+ await this.db.transaction(async () => {
217
+ const parentHadExistingChild = await hasExistingChild(this.db, this.metadata.id, entry.parentId);
218
+ const nextSeq = await getNextSequence(this.db, this.metadata.id);
219
+ await this.db
220
+ .prepare("INSERT INTO session_entries (session_id, id, entry_seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)")
221
+ .run(this.metadata.id, entry.id, nextSeq, entry.parentId, entry.type, entry.timestamp, encoded.payload);
222
+ await advanceSequence(this.db, this.metadata.id, nextSeq);
223
+ await this.db
224
+ .prepare("UPDATE session_materialized SET payload = ? WHERE session_id = ?")
225
+ .run(serializeSummary(this.materializedState), this.metadata.id);
226
+ for (const materializedEntry of entryMaterializedValues(entry)) {
227
+ await this.db
228
+ .prepare("INSERT INTO entry_materialized (session_id, entry_seq, type, payload) VALUES (?, ?, ?, ?)")
229
+ .run(this.metadata.id, nextSeq, materializedEntry.type, materializedEntry.payload);
230
+ }
231
+ this.byId.set(entry.id, entry);
232
+ this.currentLeafId = leafIdAfterEntry(entry);
233
+ await this.db
234
+ .prepare("UPDATE sessions SET active_leaf_id = ? WHERE id = ?")
235
+ .run(this.currentLeafId, this.metadata.id);
236
+ if (entry.type === "leaf") {
237
+ this.activeBranchId = null;
238
+ await this.materializeBranch(entry.targetId);
239
+ await this.appendToActiveBranch(entry.id, entry.parentId);
240
+ }
241
+ else {
242
+ if (parentHadExistingChild) {
243
+ await this.materializeBranch(entry.parentId);
244
+ }
245
+ await this.appendToActiveBranch(entry.id, entry.parentId);
246
+ }
247
+ });
248
+ }
249
+ catch (error) {
250
+ this.materializedState = previousMaterializedState;
251
+ this.labelsById = previousMaterializedState.labelsById;
252
+ this.byId = previousById;
253
+ this.currentLeafId = previousLeafId;
254
+ this.activeBranchId = previousActiveBranchId;
255
+ if (error instanceof SessionError)
256
+ throw error;
257
+ throw new SessionError("storage", `Failed to append SQLite session entry ${entry.id}`);
258
+ }
259
+ }
260
+ async getEntry(id) {
261
+ const cached = this.byId.get(id);
262
+ if (cached)
263
+ return cached;
264
+ const row = await this.db
265
+ .prepare("SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id = ?")
266
+ .get(this.metadata.id, id);
267
+ if (!row)
268
+ return undefined;
269
+ try {
270
+ const entry = decodeEntry(row);
271
+ this.byId.set(entry.id, entry);
272
+ return entry;
273
+ }
274
+ catch {
275
+ return undefined;
276
+ }
277
+ }
278
+ async findEntries(type) {
279
+ const rows = await this.db
280
+ .prepare("SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND type = ? ORDER BY entry_seq")
281
+ .all(this.metadata.id, type);
282
+ const entries = [];
283
+ for (const row of rows) {
284
+ try {
285
+ const entry = decodeEntry(row);
286
+ this.byId.set(entry.id, entry);
287
+ entries.push(entry);
288
+ }
289
+ catch {
290
+ // Keep JSONL-like permissive resume behavior: skip malformed entries.
291
+ }
292
+ }
293
+ return entries;
294
+ }
295
+ async getLabel(id) {
296
+ return this.labelsById.get(id);
297
+ }
298
+ async getSessionName() {
299
+ return this.materializedState.name;
300
+ }
301
+ async getSessionStats() {
302
+ return sessionStatsFromMaterializedState(this.materializedState);
303
+ }
304
+ async getPathToRootOrCompaction(leafId) {
305
+ if (leafId === null)
306
+ return [];
307
+ if (leafId === this.currentLeafId) {
308
+ if (!this.activeBranchId) {
309
+ throw invalidSession(`missing active branch for session ${this.metadata.id} leaf ${leafId}`);
310
+ }
311
+ return getMaterializedBranchPathOrCompaction(this.db, this.metadata.id, this.activeBranchId, this.byId);
312
+ }
313
+ return this.getPathToRootOrCompactionEntries(leafId);
314
+ }
315
+ async getEntries(options) {
316
+ const limit = options?.limit;
317
+ if (limit !== undefined) {
318
+ const beforeOrAtEntrySeq = options?.afterEntrySeq ??
319
+ (await this.db
320
+ .prepare("SELECT entry_seq FROM session_entries WHERE session_id = ? ORDER BY entry_seq DESC LIMIT 1")
321
+ .get(this.metadata.id))?.entry_seq;
322
+ if (beforeOrAtEntrySeq === undefined) {
323
+ return [];
324
+ }
325
+ const rows = await this.db
326
+ .prepare("SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND entry_seq <= ? ORDER BY entry_seq DESC LIMIT ?")
327
+ .all(this.metadata.id, beforeOrAtEntrySeq, limit);
328
+ const entries = (await decodeEntryRows(rows)).entries;
329
+ for (const entry of entries) {
330
+ this.byId.set(entry.id, entry);
331
+ }
332
+ return entries.reverse();
333
+ }
334
+ const rows = await this.db
335
+ .prepare("SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? ORDER BY entry_seq")
336
+ .all(this.metadata.id);
337
+ const entries = (await decodeEntryRows(rows)).entries;
338
+ for (const entry of entries) {
339
+ this.byId.set(entry.id, entry);
340
+ }
341
+ return entries;
342
+ }
343
+ async cleanup() {
344
+ await this.db.close();
345
+ }
346
+ }
347
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sqlite/storage/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LeafEntry, SessionEntryCursorOptions, SessionStorage, SessionTreeEntry } from \"@handy_wote/pi-agent-core\";\nimport { SessionError } from \"@handy_wote/pi-agent-core\";\nimport { uuidv7 } from \"@handy_wote/pi-ai\";\nimport type { SqliteDatabase, SqliteSessionMetadata } from \"../types.ts\";\nimport { getMaterializedBranchPathOrCompaction } from \"./branch-entries.ts\";\nimport { decodeEntry, encodeEntry, type SessionEntryRow } from \"./session-entries.ts\";\nimport {\n\tapplyEntryToMaterializedState,\n\tcreateEmptyMaterializedState,\n\ttype EntryMaterializedRow,\n\tentryMaterializedValues,\n\tmaterializedStateFromRows,\n\tmaterializedStateValues,\n\ttype SessionMaterializedRow,\n\ttype SessionMaterializedState,\n\tserializeSummary,\n\tsessionStatsFromMaterializedState,\n} from \"./session-materialized.ts\";\nimport { advanceSequence, getNextSequence } from \"./session-sequences.ts\";\nimport { rowToMetadata, type SessionRow } from \"./sessions.ts\";\nimport { generateEntryId, invalidSession, leafIdAfterEntry } from \"./shared.ts\";\n\nasync function decodeEntryRows(entryRows: SessionEntryRow[]): Promise<{\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst entries: SessionTreeEntry[] = [];\n\tlet leafId: string | null = null;\n\tfor (const entryRow of entryRows) {\n\t\ttry {\n\t\t\tconst entry = decodeEntry(entryRow);\n\t\t\tentries.push(entry);\n\t\t\tleafId = leafIdAfterEntry(entry);\n\t\t} catch {\n\t\t\t// Keep JSONL-like permissive resume behavior: skip malformed entries.\n\t\t}\n\t}\n\treturn { entries, leafId };\n}\n\nasync function loadEntryRowsByIds(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tentryIds: string[],\n): Promise<Map<string, SessionEntryRow>> {\n\tif (entryIds.length === 0) return new Map<string, SessionEntryRow>();\n\tconst placeholders = entryIds.map(() => \"?\").join(\", \");\n\tconst rows = await db\n\t\t.prepare(\n\t\t\t`SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id IN (${placeholders})`,\n\t\t)\n\t\t.all<SessionEntryRow>(sessionId, ...entryIds);\n\treturn new Map(rows.map((row) => [row.id, row]));\n}\n\nasync function loadActiveBranchId(db: SqliteDatabase, sessionId: string): Promise<string | null> {\n\t// branch_entries includes leaf navigation entries for the active branch, so the\n\t// newest branch_entries row identifies the branch that was most recently made active.\n\tconst row = await db\n\t\t.prepare(\n\t\t\t\"SELECT branch_id FROM branch_entries WHERE session_id = ? ORDER BY entry_seq DESC, branch_id DESC LIMIT 1\",\n\t\t)\n\t\t.get<{ branch_id: string }>(sessionId);\n\treturn row?.branch_id ?? null;\n}\n\nasync function hasExistingChild(db: SqliteDatabase, sessionId: string, parentId: string | null): Promise<boolean> {\n\tconst row =\n\t\tparentId === null\n\t\t\t? await db\n\t\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id IS NULL LIMIT 1\")\n\t\t\t\t\t.get<{ found: number }>(sessionId)\n\t\t\t: await db\n\t\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND parent_id = ? LIMIT 1\")\n\t\t\t\t\t.get<{ found: number }>(sessionId, parentId);\n\treturn row !== undefined;\n}\n\nasync function loadSqliteStorage(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n): Promise<{\n\trow: SessionRow;\n\tleafId: string | null;\n\tactiveBranchId: string | null;\n\tmaterializedState: SessionMaterializedState;\n}> {\n\tconst row = await db\n\t\t.prepare(\"SELECT id, created_at, metadata, cwd, parent_session_id, active_leaf_id FROM sessions WHERE id = ?\")\n\t\t.get<SessionRow>(sessionId);\n\tif (!row) throw new SessionError(\"not_found\", `Session not found: ${sessionId}`);\n\n\tconst leafId = row.active_leaf_id;\n\tconst materializedRow = await db\n\t\t.prepare(\"SELECT session_id, payload FROM session_materialized WHERE session_id = ?\")\n\t\t.get<SessionMaterializedRow>(sessionId);\n\tif (!materializedRow) throw invalidSession(`missing materialized row for session ${sessionId}`);\n\tconst entryMaterializedRows = await db\n\t\t.prepare(\n\t\t\t\"SELECT session_id, entry_seq, type, payload FROM entry_materialized WHERE session_id = ? ORDER BY entry_seq, type\",\n\t\t)\n\t\t.all<EntryMaterializedRow>(sessionId);\n\treturn {\n\t\trow,\n\t\tleafId,\n\t\tactiveBranchId: await loadActiveBranchId(db, sessionId),\n\t\tmaterializedState: materializedStateFromRows(materializedRow, entryMaterializedRows),\n\t};\n}\n\nexport class SqliteSessionStorage implements SessionStorage<SqliteSessionMetadata> {\n\tprivate readonly db: SqliteDatabase;\n\tprivate readonly metadata: SqliteSessionMetadata;\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate currentLeafId: string | null;\n\tprivate activeBranchId: string | null;\n\tprivate materializedState: SessionMaterializedState;\n\n\tprivate async getPathToRootOrCompactionEntries(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet stopAtEntryId: string | null = null;\n\t\tlet current = await this.getEntry(leafId);\n\t\tif (!current) throw new SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tif (stopAtEntryId !== null && current.id === stopAtEntryId) break;\n\t\t\tif (current.type === \"compaction\") {\n\t\t\t\tif (current.retainedTail) break;\n\t\t\t\tstopAtEntryId = current.firstKeptEntryId ?? null;\n\t\t\t}\n\t\t\tif (!current.parentId) break;\n\t\t\tconst parent = await this.getEntry(current.parentId);\n\t\t\tif (!parent) throw new SessionError(\"invalid_session\", `Entry ${current.parentId} not found`);\n\t\t\tcurrent = parent;\n\t\t}\n\t\treturn path;\n\t}\n\n\tprivate async materializeBranch(leafId: string | null): Promise<void> {\n\t\tconst branchId = uuidv7();\n\t\t// Rebuild the branch path only when branch membership changes: branch switch\n\t\t// (leaf navigation) or a new fork from a parent that already has a child.\n\t\t// Linear appends stay cheap and extend the active branch incrementally.\n\t\tconst path = await this.getPathToRootOrCompactionEntries(leafId);\n\t\tconst entryRowsById = await loadEntryRowsByIds(\n\t\t\tthis.db,\n\t\t\tthis.metadata.id,\n\t\t\tpath.map((entry) => entry.id),\n\t\t);\n\t\tfor (const entry of path) {\n\t\t\tconst entryRow = entryRowsById.get(entry.id);\n\t\t\tif (!entryRow) throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entry.id}`);\n\t\t\tawait this.db\n\t\t\t\t.prepare(\"INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)\")\n\t\t\t\t.run(this.metadata.id, branchId, entry.id, entryRow.entry_seq);\n\t\t}\n\t\tthis.activeBranchId = branchId;\n\t}\n\n\tprivate async appendToActiveBranch(entryId: string, parentId: string | null): Promise<void> {\n\t\tif (!this.activeBranchId) {\n\t\t\tawait this.materializeBranch(parentId);\n\t\t}\n\t\t// After a branch is materialized/resynced, subsequent linear appends only add the\n\t\t// new tip entry. We do not rebuild the full branch on every append.\n\t\tif (!this.activeBranchId) {\n\t\t\tthrow invalidSession(`active branch missing for session ${this.metadata.id}`);\n\t\t}\n\t\tconst entryRow = await this.db\n\t\t\t.prepare(\"SELECT entry_seq FROM session_entries WHERE session_id = ? AND id = ?\")\n\t\t\t.get<{ entry_seq: number }>(this.metadata.id, entryId);\n\t\tif (!entryRow) throw invalidSession(`missing entry row for session ${this.metadata.id} entry ${entryId}`);\n\t\tawait this.db\n\t\t\t.prepare(\"INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq) VALUES (?, ?, ?, ?)\")\n\t\t\t.run(this.metadata.id, this.activeBranchId, entryId, entryRow.entry_seq);\n\t}\n\n\tprivate constructor(\n\t\tdb: SqliteDatabase,\n\t\tmetadata: SqliteSessionMetadata,\n\t\tentries: SessionTreeEntry[] | null,\n\t\tleafId: string | null,\n\t\tactiveBranchId: string | null,\n\t\tmaterializedState: SessionMaterializedState,\n\t) {\n\t\tthis.db = db;\n\t\tthis.metadata = metadata;\n\t\tthis.byId = new Map((entries ?? []).map((entry) => [entry.id, entry]));\n\t\tthis.materializedState = materializedState;\n\t\tthis.labelsById = materializedState.labelsById;\n\t\tthis.currentLeafId = leafId;\n\t\tthis.activeBranchId = activeBranchId;\n\t}\n\n\tstatic async open(db: SqliteDatabase, metadata: SqliteSessionMetadata): Promise<SqliteSessionStorage> {\n\t\tconst loaded = await loadSqliteStorage(db, metadata.id);\n\t\treturn new SqliteSessionStorage(\n\t\t\tdb,\n\t\t\trowToMetadata(loaded.row, metadata.path),\n\t\t\tnull,\n\t\t\tloaded.leafId,\n\t\t\tloaded.activeBranchId,\n\t\t\tloaded.materializedState,\n\t\t);\n\t}\n\n\tstatic async create(\n\t\tdb: SqliteDatabase,\n\t\tpath: string,\n\t\toptions: {\n\t\t\tcwd: string;\n\t\t\tsessionId: string;\n\t\t\tparentSessionId?: string;\n\t\t\tmetadata?: Record<string, unknown>;\n\t\t},\n\t): Promise<SqliteSessionStorage> {\n\t\tconst createdAt = new Date().toISOString();\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT INTO sessions (id, created_at, metadata, cwd, parent_session_id, active_leaf_id) VALUES (?, ?, ?, ?, ?, ?)\",\n\t\t\t)\n\t\t\t.run(\n\t\t\t\toptions.sessionId,\n\t\t\t\tcreatedAt,\n\t\t\t\toptions.metadata === undefined ? null : JSON.stringify(options.metadata),\n\t\t\t\toptions.cwd,\n\t\t\t\toptions.parentSessionId ?? null,\n\t\t\t\tnull,\n\t\t\t);\n\t\tawait db.prepare(\"INSERT INTO session_sequences (session_id, next_seq) VALUES (?, ?)\").run(options.sessionId, 1);\n\t\tawait db\n\t\t\t.prepare(\"INSERT INTO session_materialized (session_id, payload) VALUES (?, ?)\")\n\t\t\t.run(...materializedStateValues(options.sessionId, createEmptyMaterializedState()));\n\t\treturn new SqliteSessionStorage(\n\t\t\tdb,\n\t\t\t{\n\t\t\t\tid: options.sessionId,\n\t\t\t\tcreatedAt,\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tpath,\n\t\t\t\tparentSessionId: options.parentSessionId,\n\t\t\t\tmetadata: options.metadata,\n\t\t\t},\n\t\t\t[],\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tcreateEmptyMaterializedState(),\n\t\t);\n\t}\n\n\tasync getMetadata(): Promise<SqliteSessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.currentLeafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !(await this.getEntry(leafId))) {\n\t\t\tthrow new SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\t}\n\t\tconst entry: LeafEntry = {\n\t\t\ttype: \"leaf\",\n\t\t\tid: await this.createEntryId(),\n\t\t\tparentId: this.currentLeafId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId: leafId,\n\t\t};\n\t\tawait this.appendEntry(entry);\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\tfor (let i = 0; i < 100; i++) {\n\t\t\tconst id = generateEntryId(this.byId);\n\t\t\tconst existing = await this.db\n\t\t\t\t.prepare(\"SELECT 1 AS found FROM session_entries WHERE session_id = ? AND id = ? LIMIT 1\")\n\t\t\t\t.get<{ found: number }>(this.metadata.id, id);\n\t\t\tif (!existing) return id;\n\t\t}\n\t\treturn uuidv7();\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tconst encoded = encodeEntry(entry);\n\t\tconst previousMaterializedState: SessionMaterializedState = {\n\t\t\t...this.materializedState,\n\t\t\tlabelsById: new Map(this.materializedState.labelsById),\n\t\t\tmodelThinkingConfigs: [...this.materializedState.modelThinkingConfigs],\n\t\t\tcurrentModel: this.materializedState.currentModel ? { ...this.materializedState.currentModel } : null,\n\t\t};\n\t\tconst previousById = new Map(this.byId);\n\t\tconst previousLeafId = this.currentLeafId;\n\t\tconst previousActiveBranchId = this.activeBranchId;\n\t\ttry {\n\t\t\tapplyEntryToMaterializedState(this.materializedState, entry);\n\t\t\tawait this.db.transaction(async () => {\n\t\t\t\tconst parentHadExistingChild = await hasExistingChild(this.db, this.metadata.id, entry.parentId);\n\t\t\t\tconst nextSeq = await getNextSequence(this.db, this.metadata.id);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT INTO session_entries (session_id, id, entry_seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.run(this.metadata.id, entry.id, nextSeq, entry.parentId, entry.type, entry.timestamp, encoded.payload);\n\t\t\t\tawait advanceSequence(this.db, this.metadata.id, nextSeq);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\"UPDATE session_materialized SET payload = ? WHERE session_id = ?\")\n\t\t\t\t\t.run(serializeSummary(this.materializedState), this.metadata.id);\n\t\t\t\tfor (const materializedEntry of entryMaterializedValues(entry)) {\n\t\t\t\t\tawait this.db\n\t\t\t\t\t\t.prepare(\"INSERT INTO entry_materialized (session_id, entry_seq, type, payload) VALUES (?, ?, ?, ?)\")\n\t\t\t\t\t\t.run(this.metadata.id, nextSeq, materializedEntry.type, materializedEntry.payload);\n\t\t\t\t}\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t\tthis.currentLeafId = leafIdAfterEntry(entry);\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\"UPDATE sessions SET active_leaf_id = ? WHERE id = ?\")\n\t\t\t\t\t.run(this.currentLeafId, this.metadata.id);\n\t\t\t\tif (entry.type === \"leaf\") {\n\t\t\t\t\tthis.activeBranchId = null;\n\t\t\t\t\tawait this.materializeBranch(entry.targetId);\n\t\t\t\t\tawait this.appendToActiveBranch(entry.id, entry.parentId);\n\t\t\t\t} else {\n\t\t\t\t\tif (parentHadExistingChild) {\n\t\t\t\t\t\tawait this.materializeBranch(entry.parentId);\n\t\t\t\t\t}\n\t\t\t\t\tawait this.appendToActiveBranch(entry.id, entry.parentId);\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.materializedState = previousMaterializedState;\n\t\t\tthis.labelsById = previousMaterializedState.labelsById;\n\t\t\tthis.byId = previousById;\n\t\t\tthis.currentLeafId = previousLeafId;\n\t\t\tthis.activeBranchId = previousActiveBranchId;\n\t\t\tif (error instanceof SessionError) throw error;\n\t\t\tthrow new SessionError(\"storage\", `Failed to append SQLite session entry ${entry.id}`);\n\t\t}\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\tconst cached = this.byId.get(id);\n\t\tif (cached) return cached;\n\t\tconst row = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND id = ?\",\n\t\t\t)\n\t\t\t.get<SessionEntryRow>(this.metadata.id, id);\n\t\tif (!row) return undefined;\n\t\ttry {\n\t\t\tconst entry = decodeEntry(row);\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\treturn entry;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\tconst rows = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND type = ? ORDER BY entry_seq\",\n\t\t\t)\n\t\t\t.all<SessionEntryRow>(this.metadata.id, type);\n\t\tconst entries: Array<Extract<SessionTreeEntry, { type: TType }>> = [];\n\t\tfor (const row of rows) {\n\t\t\ttry {\n\t\t\t\tconst entry = decodeEntry(row) as Extract<SessionTreeEntry, { type: TType }>;\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Keep JSONL-like permissive resume behavior: skip malformed entries.\n\t\t\t}\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\treturn this.materializedState.name;\n\t}\n\n\tasync getSessionStats() {\n\t\treturn sessionStatsFromMaterializedState(this.materializedState);\n\t}\n\n\tasync getPathToRootOrCompaction(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tif (leafId === this.currentLeafId) {\n\t\t\tif (!this.activeBranchId) {\n\t\t\t\tthrow invalidSession(`missing active branch for session ${this.metadata.id} leaf ${leafId}`);\n\t\t\t}\n\t\t\treturn getMaterializedBranchPathOrCompaction(this.db, this.metadata.id, this.activeBranchId, this.byId);\n\t\t}\n\t\treturn this.getPathToRootOrCompactionEntries(leafId);\n\t}\n\n\tasync getEntries(options?: SessionEntryCursorOptions): Promise<SessionTreeEntry[]> {\n\t\tconst limit = options?.limit;\n\t\tif (limit !== undefined) {\n\t\t\tconst beforeOrAtEntrySeq =\n\t\t\t\toptions?.afterEntrySeq ??\n\t\t\t\t(\n\t\t\t\t\tawait this.db\n\t\t\t\t\t\t.prepare(\"SELECT entry_seq FROM session_entries WHERE session_id = ? ORDER BY entry_seq DESC LIMIT 1\")\n\t\t\t\t\t\t.get<{ entry_seq: number }>(this.metadata.id)\n\t\t\t\t)?.entry_seq;\n\t\t\tif (beforeOrAtEntrySeq === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst rows = await this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? AND entry_seq <= ? ORDER BY entry_seq DESC LIMIT ?\",\n\t\t\t\t)\n\t\t\t\t.all<SessionEntryRow>(this.metadata.id, beforeOrAtEntrySeq, limit);\n\t\t\tconst entries = (await decodeEntryRows(rows)).entries;\n\t\t\tfor (const entry of entries) {\n\t\t\t\tthis.byId.set(entry.id, entry);\n\t\t\t}\n\t\t\treturn entries.reverse();\n\t\t}\n\t\tconst rows = await this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ? ORDER BY entry_seq\",\n\t\t\t)\n\t\t\t.all<SessionEntryRow>(this.metadata.id);\n\t\tconst entries = (await decodeEntryRows(rows)).entries;\n\t\tfor (const entry of entries) {\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync cleanup(): Promise<void> {\n\t\tawait this.db.close();\n\t}\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { SessionTreeEntry } from "@handy_wote/pi-agent-core";
2
+ export interface SessionEntryRow {
3
+ session_id: string;
4
+ id: string;
5
+ entry_seq: number;
6
+ parent_id: string | null;
7
+ type: SessionTreeEntry["type"];
8
+ timestamp: string;
9
+ payload: string;
10
+ }
11
+ export type EncodedEntry = {
12
+ payload: string;
13
+ };
14
+ export declare function validateSessionTreeEntry(entry: SessionTreeEntry): void;
15
+ export declare function encodeEntry(entry: SessionTreeEntry): EncodedEntry;
16
+ export declare function decodeEntry(row: SessionEntryRow): SessionTreeEntry;
17
+ //# sourceMappingURL=session-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-entries.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/session-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAwB,MAAM,2BAA2B,CAAC;AAGxF,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAkCF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CA8EtE;AAOD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAGjE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,gBAAgB,CA6ElE","sourcesContent":["import type { SessionTreeEntry, SessionTreeEntryBase } from \"@handy_wote/pi-agent-core\";\nimport { invalidEntry, isRecord } from \"./shared.ts\";\n\nexport interface SessionEntryRow {\n\tsession_id: string;\n\tid: string;\n\tentry_seq: number;\n\tparent_id: string | null;\n\ttype: SessionTreeEntry[\"type\"];\n\ttimestamp: string;\n\tpayload: string;\n}\n\nexport type EncodedEntry = {\n\tpayload: string;\n};\n\ntype EntryPayload<TEntry extends SessionTreeEntry> = Omit<TEntry, keyof SessionTreeEntryBase | \"type\">;\n\ntype MessagePayload = EntryPayload<Extract<SessionTreeEntry, { type: \"message\" }>>;\ntype ThinkingLevelChangePayload = EntryPayload<Extract<SessionTreeEntry, { type: \"thinking_level_change\" }>>;\ntype ModelChangePayload = EntryPayload<Extract<SessionTreeEntry, { type: \"model_change\" }>>;\ntype ActiveToolsChangePayload = EntryPayload<Extract<SessionTreeEntry, { type: \"active_tools_change\" }>>;\ntype CompactionPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"compaction\" }>>;\ntype BranchSummaryPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"branch_summary\" }>>;\ntype CustomPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"custom\" }>>;\ntype CustomMessagePayload = EntryPayload<Extract<SessionTreeEntry, { type: \"custom_message\" }>>;\ntype LabelPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"label\" }>>;\ntype SessionInfoPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"session_info\" }>>;\ntype LeafPayload = EntryPayload<Extract<SessionTreeEntry, { type: \"leaf\" }>>;\n\nfunction parsePayload(row: SessionEntryRow): unknown {\n\ttry {\n\t\treturn JSON.parse(row.payload);\n\t} catch (error) {\n\t\tthrow invalidEntry(`entry ${row.id} payload is not valid JSON`, error instanceof Error ? error : undefined);\n\t}\n}\n\nfunction isTextImageContentArray(value: unknown): boolean {\n\treturn (\n\t\tArray.isArray(value) &&\n\t\tvalue.every(\n\t\t\t(item) =>\n\t\t\t\tisRecord(item) && typeof item.type === \"string\" && (item.type !== \"text\" || typeof item.text === \"string\"),\n\t\t)\n\t);\n}\n\nexport function validateSessionTreeEntry(entry: SessionTreeEntry): void {\n\tif (typeof entry.id !== \"string\" || !entry.id) throw invalidEntry(\"entry is missing id\");\n\tif (entry.parentId !== null && typeof entry.parentId !== \"string\") {\n\t\tthrow invalidEntry(`entry ${entry.id} has invalid parentId`);\n\t}\n\tif (typeof entry.timestamp !== \"string\" || !entry.timestamp) {\n\t\tthrow invalidEntry(`entry ${entry.id} is missing timestamp`);\n\t}\n\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\tif (!isRecord(entry.message) || typeof entry.message.role !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} is missing message payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"thinking_level_change\":\n\t\t\tif (typeof entry.thinkingLevel !== \"string\") throw invalidEntry(`entry ${entry.id} is missing thinkingLevel`);\n\t\t\tbreak;\n\t\tcase \"model_change\":\n\t\t\tif (typeof entry.provider !== \"string\" || typeof entry.modelId !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid model_change payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"active_tools_change\":\n\t\t\tif (\n\t\t\t\t!Array.isArray(entry.activeToolNames) ||\n\t\t\t\tentry.activeToolNames.some((value) => typeof value !== \"string\")\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid active_tools_change payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"compaction\":\n\t\t\tif (\n\t\t\t\ttypeof entry.summary !== \"string\" ||\n\t\t\t\ttypeof entry.firstKeptEntryId !== \"string\" ||\n\t\t\t\ttypeof entry.tokensBefore !== \"number\" ||\n\t\t\t\t(entry.retainedTail !== undefined && !Array.isArray(entry.retainedTail))\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid compaction payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"branch_summary\":\n\t\t\tif (typeof entry.fromId !== \"string\" || typeof entry.summary !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid branch_summary payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"custom\":\n\t\t\tif (typeof entry.customType !== \"string\") throw invalidEntry(`entry ${entry.id} has invalid custom payload`);\n\t\t\tbreak;\n\t\tcase \"custom_message\":\n\t\t\tif (\n\t\t\t\ttypeof entry.customType !== \"string\" ||\n\t\t\t\ttypeof entry.display !== \"boolean\" ||\n\t\t\t\t!(typeof entry.content === \"string\" || isTextImageContentArray(entry.content))\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid custom_message payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"label\":\n\t\t\tif (typeof entry.targetId !== \"string\" || (entry.label !== undefined && typeof entry.label !== \"string\")) {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid label payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"session_info\":\n\t\t\tif (entry.name !== undefined && typeof entry.name !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid session_info payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"leaf\":\n\t\t\tif (entry.targetId !== null && typeof entry.targetId !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${entry.id} has invalid leaf payload`);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\tconst exhaustive: never = entry;\n\t\t\tthrow invalidEntry(`unknown entry type ${(exhaustive as { type?: string }).type ?? \"unknown\"}`);\n\t\t}\n\t}\n}\n\nfunction entryToPayload<TEntry extends SessionTreeEntry>(entry: TEntry): EntryPayload<TEntry> {\n\tconst { type: _type, id: _id, parentId: _parentId, timestamp: _timestamp, ...payload } = entry;\n\treturn payload as EntryPayload<TEntry>;\n}\n\nexport function encodeEntry(entry: SessionTreeEntry): EncodedEntry {\n\tvalidateSessionTreeEntry(entry);\n\treturn { payload: JSON.stringify(entryToPayload(entry)) };\n}\n\nexport function decodeEntry(row: SessionEntryRow): SessionTreeEntry {\n\tconst payload = parsePayload(row);\n\tif (!isRecord(payload)) throw invalidEntry(`entry ${row.id} payload is not an object`);\n\tconst base = {\n\t\tid: row.id,\n\t\tparentId: row.parent_id,\n\t\ttimestamp: row.timestamp,\n\t};\n\n\tswitch (row.type) {\n\t\tcase \"message\": {\n\t\t\tif (!(\"message\" in payload)) throw invalidEntry(`entry ${row.id} is missing message payload`);\n\t\t\tconst messagePayload = payload as MessagePayload;\n\t\t\treturn { ...base, type: \"message\", ...messagePayload };\n\t\t}\n\t\tcase \"thinking_level_change\":\n\t\t\tif (typeof payload.thinkingLevel !== \"string\") throw invalidEntry(`entry ${row.id} is missing thinkingLevel`);\n\t\t\treturn { ...base, type: \"thinking_level_change\", ...(payload as ThinkingLevelChangePayload) };\n\t\tcase \"model_change\":\n\t\t\tif (typeof payload.provider !== \"string\" || typeof payload.modelId !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid model_change payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"model_change\", ...(payload as ModelChangePayload) };\n\t\tcase \"active_tools_change\":\n\t\t\tif (\n\t\t\t\t!Array.isArray(payload.activeToolNames) ||\n\t\t\t\tpayload.activeToolNames.some((value) => typeof value !== \"string\")\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid active_tools_change payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"active_tools_change\", ...(payload as ActiveToolsChangePayload) };\n\t\tcase \"compaction\":\n\t\t\tif (\n\t\t\t\ttypeof payload.summary !== \"string\" ||\n\t\t\t\ttypeof payload.firstKeptEntryId !== \"string\" ||\n\t\t\t\ttypeof payload.tokensBefore !== \"number\" ||\n\t\t\t\t(payload.retainedTail !== undefined && !Array.isArray(payload.retainedTail))\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid compaction payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"compaction\", ...(payload as CompactionPayload) };\n\t\tcase \"branch_summary\":\n\t\t\tif (typeof payload.fromId !== \"string\" || typeof payload.summary !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid branch_summary payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"branch_summary\", ...(payload as BranchSummaryPayload) };\n\t\tcase \"custom\":\n\t\t\tif (typeof payload.customType !== \"string\") throw invalidEntry(`entry ${row.id} has invalid custom payload`);\n\t\t\treturn { ...base, type: \"custom\", ...(payload as CustomPayload) };\n\t\tcase \"custom_message\":\n\t\t\tif (\n\t\t\t\ttypeof payload.customType !== \"string\" ||\n\t\t\t\ttypeof payload.display !== \"boolean\" ||\n\t\t\t\t!(\"content\" in payload)\n\t\t\t) {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid custom_message payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"custom_message\", ...(payload as CustomMessagePayload) };\n\t\tcase \"label\":\n\t\t\tif (typeof payload.targetId !== \"string\") throw invalidEntry(`entry ${row.id} has invalid label payload`);\n\t\t\tif (payload.label !== undefined && typeof payload.label !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid label payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"label\", ...(payload as LabelPayload) };\n\t\tcase \"session_info\":\n\t\t\tif (payload.name !== undefined && typeof payload.name !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid session_info payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"session_info\", ...(payload as SessionInfoPayload) };\n\t\tcase \"leaf\":\n\t\t\tif (payload.targetId !== null && typeof payload.targetId !== \"string\") {\n\t\t\t\tthrow invalidEntry(`entry ${row.id} has invalid leaf payload`);\n\t\t\t}\n\t\t\treturn { ...base, type: \"leaf\", ...(payload as LeafPayload) };\n\t\tdefault:\n\t\t\tthrow invalidEntry(`unknown entry type ${row.type}`);\n\t}\n}\n"]}