docs: rebalance prose cleanup and add trimming skill

This commit is contained in:
Tianyi Cui
2026-07-13 23:27:00 +08:00
parent fcdc318dda
commit 148046b9c8
392 changed files with 2801 additions and 1754 deletions

View File

@@ -1,5 +1,7 @@
/**
* SQLite durable session-persistence backend (`@deepseek-ai/dsh-session-persistence-sqlite`).
* SQLite durable session-persistence backend. It maps each session header and
* event to rows, and delegates write-path orchestration to
* {@link PersistenceCoordinator}.
* @module @deepseek-ai/dsh-session-persistence-sqlite
*/
@@ -75,8 +77,8 @@ export class SessionPersistenceSqlite extends SessionPersistence implements Pers
constructor(ctx: Context, public config: Config) {
super(ctx)
// Open the database asynchronously (the parent directory may need creating); every hook
// awaits `ready` first.
// Open asynchronously so directory creation does not block plugin apply;
// every storage hook awaits the same readiness promise.
this.ready = this.openDb(config.path, (config as Required<Config>).journalMode)
this.coordinator = new PersistenceCoordinator<number>(this.ctx, this)
}
@@ -105,8 +107,8 @@ export class SessionPersistenceSqlite extends SessionPersistence implements Pers
return this.coordinator.load(id)
}
// `list` is BOTH the public service method and the PersistenceBackend hook — one method (the
// SELECT below).
// One method serves both public `list` and the backend hook; delegating it to
// the coordinator would call this hook recursively.
/**
* The per-session init promises, exposed for white-box tests that await a

View File

@@ -56,7 +56,9 @@ export interface EventRow {
export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
/**
* Open the database, validate its version, and apply schema and pragmas.
* Open the database and apply its schema and pragmas. A zero `user_version` is
* stamped with {@link SCHEMA_VERSION}; every other non-current version rejects
* rather than being migrated in place.
* @param path - the SQLite database file to open (created when absent).
* @param journalMode - validated journal pragma.
* @returns the open handle with pragmas applied and both tables ensured.
@@ -140,9 +142,10 @@ export function rowToEvent(row: EventRow): SessionEvent {
}
/**
* The preserved prefix of an ordered event-row list (mirrors the JSONL backend's `scanLog`):
* the longest prefix of complete, seq-contiguous, parseable rows, PLUS the seq from which a
* never-committed torn tail must be deleted (or `undefined` if the whole list is intact).
* Find the preserved prefix of ordered event rows. Fully written rows in an
* interrupted final turn remain in the prefix. The first unparsable row or seq
* gap after the last `turn/end` marks a tolerated torn tail; the same hole in
* the committed region rejects.
*
* @param rows - one session's event rows, ordered by seq ascending.
* @returns the preserved event prefix, plus `tornFrom` — the seq the physical
@@ -167,7 +170,8 @@ export function scanRows(rows: readonly EventRow[]): { preserved: SessionEvent[]
if (parsed[i]?.ok && rows[i]?.type === 'turn/end') { lastTurnEnd = i; break }
}
// Walk the longest PREFIX of complete, seq-contiguous, parseable rows (row i has seq === i).
// Preserve the contiguous prefix, including a complete interrupted turn;
// holes through the last committed boundary throw, while later holes stop.
const preserved: SessionEvent[] = []
for (let i = 0; i < rows.length; i++) {
const p = parsed[i]

View File

@@ -28,8 +28,7 @@ async function backend(path = ':memory:'): Promise<{ ctx: Context; dispose: () =
return { ctx, dispose: () => fiber.dispose() }
}
// The payoff: the SAME backend-agnostic contract the JSONL backend runs, now
// proving the SQLite backend satisfies identical semantics.
// Run the same backend-agnostic contract as JSONL to pin identical semantics.
runPersistenceContract('sqlite', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
@@ -40,7 +39,8 @@ runPersistenceContract('sqlite', async () => {
}
})
// Run the shared coordinator orchestration suite against the real SQLite backend.
// A file-backed database lets two mounts share rows across reload. `corruptTail` inserts invalid
// JSON past the committed seq, exercising coordinator repair against real database rows.
runCoordinatorContract('sqlite', async (): Promise<CoordinatorFixture> => {
const dir = await mkdtemp(join(tmpdir(), 'dsh-sqlite-coord-'))
const path = join(dir, 'sessions.db')
@@ -63,7 +63,8 @@ runCoordinatorContract('sqlite', async (): Promise<CoordinatorFixture> => {
describe('scanRows', () => {
// scanRows works off EventRows (data is a JSON string column); build them from SessionEvents
// so the unit tests read in terms of the event vocabulary.
// so the unit tests read in terms of the event vocabulary. Surface metadata is serialized to
// its nullable columns so the conversion remains faithful.
const rows = (events: SessionEvent[]): EventRow[] =>
events.map((e) => {
const se = e as SessionEvent<SurfaceEventType>
@@ -252,7 +253,8 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
it('rejects a sibling v3 database (the merge-collided version) rather than opening it against missing columns', async () => {
// Two unmerged branches each shipped a DISTINCT layout under user_version 3 (one added only
// `seed_length`, the other only the surface columns).
// `seed_length`, the other only the surface columns). The merged v4 cannot interpret that
// ambiguous, incomplete layout and must reject it.
const path = await freshDbPath()
openDatabase(path, 'wal').close() // creates + stamps user_version = SCHEMA_VERSION (4)
const db = openDatabase(path, 'wal')
@@ -269,7 +271,9 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
await b1.ctx.sessionPersistence.append(m.id, oneTurnLog()) // committed: seqs 0..5
await b1.dispose()
// Hand-insert a torn tail row (seq 6, no closing turn/end) whose `data` is invalid JSON.
// A torn row after the last committed turn has invalid JSON. `scanRows` locates the boundary
// from seq/type columns without parsing the tail, preserves the committed prefix, and load
// deletes the row; invalid JSON inside the committed region would remain fatal.
const db = openDatabase(path, 'wal')
db.prepare('INSERT INTO events (session_id, seq, type, time, data) VALUES (?, 6, ?, 7, ?)')
.run(m.id, 'turn/start', '{not valid json')