Merge remote-tracking branch 'origin/master' into worktree/session-reference
# Conflicts: # docs/capability-seams.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/cordis/tool-cordis/src/api-catalog.ts # packages/ui/acp/README.md # packages/ui/acp/package.json # packages/ui/acp/tsconfig.json # packages/ui/tui/package.json # packages/ui/tui/src/index.ts # packages/ui/tui/tests/tui.spec.ts # packages/ui/tui/tsconfig.json # pnpm-lock.yaml # python/sdk-runtime/package.json # scripts/gen-doc-graphs.ts # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# session-query/ — session retrieval capability family
|
||||
|
||||
Trusted exact reads and relationship traces over live and durable session logs. The family contains one interface package that owns `ctx.sessionQuery`, logical-corpus precedence, surface classification, bounded event reads, lineage, and direct event relationships.
|
||||
Trusted exact reads and relationship traces over live and durable session logs. The family contains one interface package that owns `ctx.sessionQuery`, logical-corpus precedence, title folding, surface classification, bounded event reads, lineage, and direct event relationships.
|
||||
|
||||
| Package | Role | ctx key |
|
||||
|---|---|---|
|
||||
| [`session-query/`](session-query/README.md) | Logical-corpus exact-read and relationship-tracing service | `ctx.sessionQuery` |
|
||||
| [`session-query/`](session-query/README.md) | Logical-corpus title, event, lineage, and relationship reads | `ctx.sessionQuery` |
|
||||
|
||||
The family is independent of compaction: it reads canonical lineage, surface operations, and logged provenance but does not participate in compaction policy or execution. Full-text search remains a proposed SQLite package rather than a speculative provider seam in this interface package.
|
||||
|
||||
@@ -5,13 +5,14 @@ Exact session-history retrieval and relationship tracing through `ctx.sessionQue
|
||||
## Reads
|
||||
|
||||
- `listSessions()` reads current persistence metadata, merges live records with live precedence, and returns cloned records in deterministic newest-first 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.
|
||||
- `listEvents(sessionId)` loads the live-preferred raw log and classifies each event as `current`, `shadowed`, or `log-only` with the shared `dsh-session` surface fold.
|
||||
- `readSurface(sessionId)` returns one cloned header, raw-log capture boundary, and the complete folded current surface in model-history order. A live session wins over persistence; compaction is observed before or after its replacement append, never as a synthetic mixture.
|
||||
- `readEvent(request)` returns a cloned header, the full target event, and a bounded raw-seq window. `before` and `after` default to zero and may not exceed `readWindowMax`.
|
||||
- `traceSession(sessionId)` reads the corpus once and returns immediate-to-outward ancestors plus deterministic recursive descendant trees. `complete: false` identifies the first missing parent; a target-connected cycle fails with `SESSION_QUERY_INVALID_LINEAGE`.
|
||||
- `traceEvent(request)` loads the logical log once and returns direct positional replacements and direct logged provenance. `replacementChain` follows positional replacers to the final replacement; provenance links remain non-transitive.
|
||||
|
||||
Persistence is optional and may mount or unmount dynamically. Cross-corpus listing and lineage tracing fail with `SESSION_QUERY_PERSISTENCE_FAILED` while mounted persistence is unreadable. An event read or trace targeting a known live session does not consult persistence, so durable backend health cannot make current in-memory history unreadable. Persisted event operations list before loading and reject a metadata mismatch rather than combining inconsistent observations.
|
||||
Persistence is optional and may mount or unmount dynamically. Cross-corpus listing and lineage tracing fail with `SESSION_QUERY_PERSISTENCE_FAILED` while mounted persistence is unreadable. A title, event read, or trace targeting a known live session does not consult persistence, so durable backend health cannot make current in-memory state unreadable. Persisted title and event operations list before loading and reject a metadata mismatch rather than combining inconsistent observations. `listSessions()` remains lightweight and does not load logs or index titles.
|
||||
|
||||
`listEvents()`, `readSurface()`, and `traceEvent()` run the same one-pass `dsh-session` surface fold. A loaded log is valid only when event seqs are zero-based and contiguous, surface markers obey event-type eligibility, provenance arrays are nonempty and duplicate-free, references name earlier events, and each positional replacement names and cites every surface node it removes; every violation fails with `SESSION_QUERY_INVALID_SURFACE`.
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-title": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-persistence": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
@@ -45,6 +46,7 @@
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-title": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { Context, Service } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import 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 {
|
||||
SessionEventReadRequest,
|
||||
SessionEventRecord,
|
||||
@@ -65,6 +67,16 @@ export class SessionQueryService extends Service {
|
||||
return this._corpus.listSessions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold the latest log-backed title from one live-preferred logical session.
|
||||
* @param sessionId - live or persisted session id to read.
|
||||
* @returns latest title snapshot, or `undefined` when the log has no title event.
|
||||
*/
|
||||
async readTitle(sessionId: SessionId): Promise<SessionTitleSnapshot | undefined> {
|
||||
const loaded = await this._corpus.load(sessionId)
|
||||
return foldSessionTitle(loaded.events)
|
||||
}
|
||||
|
||||
/**
|
||||
* List lightweight raw-log event records for one logical session.
|
||||
* @param sessionId - live-preferred session id to read.
|
||||
|
||||
@@ -6,6 +6,7 @@ import SessionPersistence from '@deepseek-ai/dsh-session-persistence'
|
||||
import SessionQueryService, {
|
||||
type SessionQueryErrorCode,
|
||||
} from '@deepseek-ai/dsh-session-query'
|
||||
import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title'
|
||||
|
||||
function header(id: string, createdAt = 1, extra: Partial<SessionHeader> = {}): SessionHeader {
|
||||
return { version: SESSION_FORMAT_VERSION, id: SessionId(id), createdAt, ...extra }
|
||||
@@ -85,6 +86,58 @@ function rejectUnknown<T>(reason: unknown): Promise<T> {
|
||||
}
|
||||
|
||||
describe('session-query exact reads', () => {
|
||||
it('reads the latest title from one live-preferred or persisted log without widening listSessions', async () => {
|
||||
const persistedHeader = header('persisted-title', 2)
|
||||
const sharedHeader = header('shared-title', 3)
|
||||
TestPersistence.reset([
|
||||
{
|
||||
meta: persistedHeader,
|
||||
events: [{
|
||||
type: 'session/title',
|
||||
seq: 0,
|
||||
time: 20,
|
||||
data: {
|
||||
title: 'Persisted title',
|
||||
messageSeqs: [4],
|
||||
source: { kind: 'fallback' },
|
||||
},
|
||||
}],
|
||||
},
|
||||
{
|
||||
meta: sharedHeader,
|
||||
events: [{
|
||||
type: 'session/title',
|
||||
seq: 0,
|
||||
time: 30,
|
||||
data: {
|
||||
title: 'Stale durable title',
|
||||
messageSeqs: [1],
|
||||
source: { kind: 'fallback' },
|
||||
},
|
||||
}],
|
||||
},
|
||||
])
|
||||
const ctx = await liveContext()
|
||||
const shared = ctx.sessions.create(sharedHeader.id, { meta: { createdAt: 3 } })
|
||||
shared.append('session/title', {
|
||||
title: 'Live title',
|
||||
messageSeqs: [7],
|
||||
source: {
|
||||
kind: 'provider',
|
||||
provider: SessionTitleProviderId('query-test'),
|
||||
},
|
||||
})
|
||||
await ctx.plugin(TestPersistence)
|
||||
|
||||
await expect(ctx.sessionQuery.readTitle(persistedHeader.id)).resolves.toMatchObject({
|
||||
title: 'Persisted title', eventSeq: 0, updatedAt: 20,
|
||||
})
|
||||
await expect(ctx.sessionQuery.readTitle(shared.id)).resolves.toMatchObject({
|
||||
title: 'Live title', eventSeq: 0,
|
||||
})
|
||||
expect(Object.keys((await ctx.sessionQuery.listSessions())[0]!)).toEqual(['header', 'live', 'persisted'])
|
||||
})
|
||||
|
||||
it('lists live sessions deterministically and returns detached headers', async () => {
|
||||
const ctx = await liveContext()
|
||||
const older = ctx.sessions.create(SessionId('older'), { meta: { createdAt: 1 } })
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../session-title/session-title"
|
||||
},
|
||||
{
|
||||
"path": "../../session-persistence/session-persistence"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user