Merge origin/master into codex/sqlite-metadata-contracts

This commit is contained in:
Tianyi Cui
2026-07-24 21:07:23 +08:00
289 changed files with 7383 additions and 1681 deletions

View File

@@ -5,6 +5,7 @@
## Reads
- `listSessions()` reads current persistence metadata, merges live records with live precedence, and returns cloned records in deterministic newest-first order.
- `readSession(sessionId)` returns one complete detached raw log after the same core replay validation used by resume; it never enters the session into the live store.
- `filterSessions(filters)` applies provider-independent session metadata and availability predicates to that same cloned logical corpus.
- `filterEvents(sessionId, filters)` extracts first-party semantic documents and applies provider-independent metadata and literal-text predicates in ascending seq order.
- `readTitle(sessionId)` loads one live-preferred or persisted log and folds its latest `session/title` event into a `SessionTitleSnapshot`; it returns `undefined` when the known session has no title.

View File

@@ -14,7 +14,6 @@ export function extractSessionEventText(event: SessionEvent): string {
switch (event.type) {
case 'user/message':
case 'assistant/message':
case 'context/message':
case 'steering/message':
return contentText(event.data.content)
case 'prompt/blocked':

View File

@@ -5,7 +5,7 @@
*/
import { Context, Service } from 'cordis'
import type { SessionId } from '@deepseek-ai/dsh-session'
import { Session, type SessionId } from '@deepseek-ai/dsh-session'
import { foldSessionTitle } from '@deepseek-ai/dsh-session-title'
import type { SessionTitleSnapshot } from '@deepseek-ai/dsh-session-title'
import type {
@@ -19,6 +19,7 @@ import type {
SessionEventTraceRequest,
SessionEventWindow,
SessionLineageTrace,
SessionLogSnapshot,
SessionRecord,
SessionResultFilter,
SessionSearchExecContext,
@@ -118,6 +119,21 @@ export abstract class SessionQueryService extends Service {
return this._corpus.listSessions()
}
/**
* Read and replay-validate one complete logical session log without making it live.
* @param sessionId - live or persisted session id to read.
* @returns cloned header and complete raw event log from one observation.
* @throws when persistence, header compatibility, or replay validation fails.
*/
async readSession(sessionId: SessionId): Promise<SessionLogSnapshot> {
const loaded = await this._corpus.load(sessionId)
new Session(sessionId, loaded.events, loaded.header)
return {
session: structuredClone(loaded.header),
events: loaded.events.map(event => structuredClone(event)),
}
}
/**
* Filter the complete logical corpus with provider-independent predicates.
* @param filters - ANDed session metadata and availability clauses.

View File

@@ -39,6 +39,14 @@ export interface SessionSurfaceSnapshot {
events: SurfaceEvent[]
}
/** One validated detached observation of a logical session's complete raw log. */
export interface SessionLogSnapshot {
/** Cloned session header selected from the same observation as `events`. */
session: SessionHeader
/** Cloned contiguous raw events after persistence repair and replay validation. */
events: SessionEvent[]
}
/** Lightweight metadata for one event within a logical session. */
export interface SessionEventRecord {
/** Session that owns the event. */

View File

@@ -44,7 +44,7 @@ describe('session-query semantic extraction', () => {
const events: SessionEvent[] = [
{ type: 'user/message', seq: 0, time: 1, data: { content: messageContent, source: { kind: 'user' } }, surfaceOp: 'append' },
{ type: 'assistant/message', seq: 1, time: 2, data: { turn: 1, step: 1, content: messageContent, provenance: { provider: 'mock', model: 'mock' } }, surfaceOp: 'append' },
{ type: 'context/message', seq: 2, time: 3, data: { content: messageContent, source: { kind: 'plugin', plugin: 'test' } }, surfaceOp: 'append' },
{ type: 'user/message', seq: 2, time: 3, data: { content: messageContent, source: { kind: 'plugin', plugin: 'test' } }, surfaceOp: 'append' },
{ type: 'steering/message', seq: 3, time: 4, data: { turn: 1, content: messageContent, source: { kind: 'user' } }, surfaceOp: 'append' },
{ type: 'prompt/blocked', seq: 4, time: 5, data: { content: [{ type: 'text', text: 'unsafe' }], source: { kind: 'user' }, reason: 'policy' } },
{ type: 'tool/call', seq: 5, time: 6, data: { turn: 1, step: 1, callId, name: 'bash', arguments: '{"cmd":"pwd"}' } },

View File

@@ -105,6 +105,25 @@ function rejectUnknown<T>(reason: unknown): Promise<T> {
}
describe('session-query exact reads', () => {
it('returns a detached replay-valid full log and rejects a corrupt persisted seed', async () => {
const valid = header('valid-log', 2)
const corrupt = header('corrupt-log', 1)
const validEvents = eventLog('valid')
const corruptEvents = [{ ...eventLog('bad')[0]!, seq: 1 }]
TestPersistence.reset([
{ meta: valid, events: validEvents },
{ meta: corrupt, events: corruptEvents },
])
const ctx = await liveContext()
await ctx.plugin(TestPersistence)
const snapshot = await ctx.sessionQuery.readSession(valid.id)
expect(snapshot).toEqual({ session: valid, events: validEvents })
Object.assign(snapshot.events[0]!, { time: 999 })
expect(TestPersistence.entries.get(valid.id)?.events[0]?.time).toBe(10)
await expect(ctx.sessionQuery.readSession(corrupt.id)).rejects.toThrow('seed event at index 0 has seq 1')
})
it('prefers a live owner that attaches while its persisted prefix is inspected', async () => {
const shared = header('attach-during-inspect', 2)
TestPersistence.reset([{ meta: shared, events: eventLog('persisted') }])

View File

@@ -116,7 +116,7 @@ function appendTraceEvents(session: Session): void {
{ surfaceOp: { op: 'replace', start: 3, end: 3 }, sourceEventSeqs: [3, 2] },
)
session.append(
'context/message',
'user/message',
{ content: [{ type: 'text', text: 'context' }], source: { kind: 'plugin', plugin: 'test' } },
{ surfaceOp: 'append' },
)
@@ -318,14 +318,14 @@ describe('session event tracing', () => {
const live = ctx.sessions.create(durable.id, { meta: { createdAt: 1, cwd: '/same' } })
live.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
live.append(
'context/message',
'user/message',
{ content: [{ type: 'text', text: 'live' }], source: { kind: 'plugin', plugin: 'test' } },
{ surfaceOp: 'append' },
)
TracePersistence.listFailure = new Error('list unavailable')
TracePersistence.inspectFailure = new Error('inspect unavailable')
await expect(ctx.sessionQuery.traceEvent({ sessionId: durable.id, seq: 1 }))
.resolves.toMatchObject({ target: { type: 'context/message' } })
.resolves.toMatchObject({ target: { type: 'user/message' } })
expect([TracePersistence.listCalls, TracePersistence.inspectCalls]).toEqual([1, 1])
TracePersistence.reset([{ meta: durable, events: [appendEvent(0)] }])