Files
deepseek-harness/packages/experimental/agent-team/tests/test-session-query.ts
T
Turtle bec6805d6a refactor(session-persistence)!: handle-based seam with a lifecycle-owned write path
The persistence seam is now create/open/stat/list returning per-session
SessionHandles (read/append/flush/close); every log read and write flows
through the owning handle. The seam package exports only the service and
handle contracts, consumer-visible errors, and pure durable-data
validation helpers; each backend owns its complete storage runtime, and
the shared contract suites pin equivalent observable behavior. The
backend routes published sessions' live events by id into the active
write handle; agent-loop only acquires, seeds, and closes the handle.
Resume appends interruptedTurnClosers through its write handle;
session-query owns the revision-keyed cold cache. Legacy-only surfaces
are removed in the same swap: locate/readRaw/supportsRawArtifacts, the
legacy event-shape read migration, zstd torn-frame salvage,
DSH_SESSION_JSONL, and hook transcript_path population; a torn final
zstd frame is discarded whole; the session-list cold blank probe returns
on stat metadata (eventCount derived from the last physical row,
sizeBytes). The WebUI ZIP export serializes the logical log from a read
handle, so both backends export identically.

Refs #3245
2026-09-01 23:19:02 +08:00

61 lines
2.0 KiB
TypeScript

/** Minimal concrete Session query for Agent Team continuation tests. */
import { SessionLogOffset } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
import SessionQueryEngine from '@deepseek-ai/dsh-session-query'
import type { SessionObservation, SessionObservationOptions } from '@deepseek-ai/dsh-session-query'
/** Undisposable immutable cut over one session's header and events. */
function cut(
source: 'live' | 'prepared',
header: SessionHeader,
events: readonly SessionEvent[],
): SessionObservation {
const lease = (): SessionObservation => ({
source,
header,
inheritedEventCount: SessionLogOffset(0),
events,
cursor: events.at(-1)?.seq ?? -1,
retain: lease,
[Symbol.dispose]: () => {},
})
return lease()
}
/** Session query implementation whose search faces are outside these tests. */
export class TestSessionQuery extends SessionQueryEngine {
static override inject = ['sessions', 'sessionPersistence']
/** Live-preferred observation backed directly by a short-lived persistence read handle. */
override async observeSession(
sessionId: SessionId,
options: SessionObservationOptions = {},
): Promise<SessionObservation> {
const live = this.ctx.sessions.get(sessionId)
if (live !== undefined) return cut('live', live.header, live.snapshotEvents())
const handle = await this.ctx.sessionPersistence.open(
sessionId,
'read',
options.signal === undefined ? {} : { signal: options.signal },
)
try {
return cut(
'prepared',
handle.header,
await handle.read(0, undefined, options.signal === undefined ? {} : { signal: options.signal }),
)
} finally {
await handle.close()
}
}
override searchSessions(): Promise<never> {
return Promise.reject(new Error('session search is not configured in this test'))
}
override searchEvents(): Promise<never> {
return Promise.reject(new Error('event search is not configured in this test'))
}
}