Merge remote-tracking branch 'origin/master' into session-query-search

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-10-session-query-service.md
#	.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md
#	.agents/notes/proposed/feature/2026-07-10-sqlite-session-query-provider.md
#	docs/architecture.md
#	docs/capability-seams.md
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/core-data-structures/persistence.md
#	docs/core-data-structures/session-query.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	packages/README.md
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/hooks/hooks-claude/tests/coverage.spec.ts
#	packages/session-persistence/session-persistence-jsonl/src/index.ts
#	packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts
#	packages/session-persistence/session-persistence-sqlite/README.md
#	packages/session-persistence/session-persistence-sqlite/src/index.ts
#	packages/session-persistence/session-persistence-sqlite/src/schema.ts
#	packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts
#	packages/session-persistence/session-persistence/README.md
#	packages/session-persistence/session-persistence/package.json
#	packages/session-query/README.md
#	packages/session-query/session-query/README.md
#	packages/session-query/session-query/package.json
#	packages/session-query/session-query/src/config.ts
#	packages/session-query/session-query/src/index.ts
#	packages/session-query/session-query/src/types.ts
#	pnpm-lock.yaml
#	scripts/gen-doc-graphs.ts
#	scripts/type-equiv.manifest.json
#	tsconfig.host.json
#	tsconfig.json
This commit is contained in:
Hypatia May
2026-07-23 13:56:56 +08:00
2630 changed files with 198302 additions and 30288 deletions

View File

@@ -38,6 +38,10 @@ Abort signals stop queued work and caller waits around asynchronous source obser
None, as this trusted search backend returns hits only to callers and registers no model-facing prompt, schema, tool, or message.
#### KV Cache effect
None; this package neither assembles nor sends a provider request.
## Known Limitations and Deferred Work
- **No caller authorization** — this is a trusted context-wide service; a model tool or UI must enforce its own access policy.

View File

@@ -11,21 +11,27 @@
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-session-persistence": "^0.0.1",
"@deepseek-ai/dsh-session-query": "^0.0.1",
"cordis": "^4.0.0-rc.6"
"cordis": "^4.0.0-rc.7"
},
"peerDependenciesMeta": {
"@deepseek-ai/dsh-session-persistence": {
@@ -37,10 +43,11 @@
},
"devDependencies": {
"@cordisjs/plugin-loader": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-session-persistence-sqlite": "workspace:^",
"@deepseek-ai/dsh-session-query": "workspace:^",
"cordis": "^4.0.0-rc.6"
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -133,6 +133,7 @@ interface SearchRow {
cwd: string | null
parent_session: string | null
seed_length: number | null
delegation_depth: number | null
live: number
persisted: number
seq: number
@@ -486,8 +487,8 @@ export class SessionSearchSqlite extends SessionSearchService {
const db = this._requireDb()
db.prepare(`
INSERT INTO persisted_sessions
(id, version, created_at, cwd, parent_session, seed_length, revision, generation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, revision, generation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
entry.header.id,
entry.header.version,
@@ -495,6 +496,7 @@ export class SessionSearchSqlite extends SessionSearchService {
entry.header.cwd ?? null,
entry.header.parentSession ?? null,
entry.header.seedLength ?? null,
entry.header.delegationDepth ?? null,
revision,
generation,
)
@@ -521,8 +523,8 @@ export class SessionSearchSqlite extends SessionSearchService {
const db = this._requireDb()
db.prepare(`
INSERT INTO temp.live_sessions
(id, version, created_at, cwd, parent_session, seed_length, fingerprint, generation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, fingerprint, generation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
entry.header.id,
entry.header.version,
@@ -530,6 +532,7 @@ export class SessionSearchSqlite extends SessionSearchService {
entry.header.cwd ?? null,
entry.header.parentSession ?? null,
entry.header.seedLength ?? null,
entry.header.delegationDepth ?? null,
entry.fingerprint,
generation,
)
@@ -673,6 +676,7 @@ function selectedDocumentsSql(): { sql: string } {
ps.cwd AS cwd,
ps.parent_session AS parent_session,
ps.seed_length AS seed_length,
ps.delegation_depth AS delegation_depth,
0 AS live,
1 AS persisted,
CAST(pd.seq AS INTEGER) AS seq,
@@ -694,6 +698,7 @@ function selectedDocumentsSql(): { sql: string } {
ls.cwd AS cwd,
ls.parent_session AS parent_session,
ls.seed_length AS seed_length,
ls.delegation_depth AS delegation_depth,
1 AS live,
CASE WHEN ? = 1 AND EXISTS (
SELECT 1 FROM persisted_sessions AS ps WHERE ps.id = ld.session_id
@@ -792,6 +797,7 @@ function sameHeader(a: SessionHeader, b: SessionHeader): boolean {
&& a.cwd === b.cwd
&& a.parentSession === b.parentSession
&& a.seedLength === b.seedLength
&& (a.delegationDepth ?? 0) === (b.delegationDepth ?? 0)
}
function rowHeader(row: SearchRow): SessionHeader {
@@ -802,6 +808,7 @@ function rowHeader(row: SearchRow): SessionHeader {
...row.cwd === null ? {} : { cwd: row.cwd },
...row.parent_session === null ? {} : { parentSession: row.parent_session as SessionId },
...row.seed_length === null ? {} : { seedLength: row.seed_length },
...row.delegation_depth === null ? {} : { delegationDepth: row.delegation_depth },
}
}

View File

@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-session-query-sqlite`.
* @module @deepseek-ai/dsh-session-query-sqlite/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-session-query-sqlite'
/** Cordis companion plugin name. */
export const name = 'session-query-sqlite-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: reconciliation, cursor generations, and derived-index
* ownership are validated at each serialized query boundary.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -5,7 +5,7 @@ import { mkdir } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
/** Current derived-index schema version. Incompatible versions reset in place. */
export const SESSION_QUERY_SQLITE_SCHEMA_VERSION = 2
export const SESSION_QUERY_SQLITE_SCHEMA_VERSION = 3
/** SQLite application id protecting unrelated databases from derived resets. */
export const SESSION_QUERY_SQLITE_APPLICATION_ID = 0x44534851
@@ -79,6 +79,7 @@ function ensurePersistentSchema(db: DatabaseSync): void {
cwd TEXT,
parent_session TEXT,
seed_length INTEGER,
delegation_depth INTEGER,
revision TEXT NOT NULL,
generation INTEGER NOT NULL
) STRICT
@@ -107,6 +108,7 @@ function ensureTemporarySchema(db: DatabaseSync): void {
cwd TEXT,
parent_session TEXT,
seed_length INTEGER,
delegation_depth INTEGER,
fingerprint TEXT NOT NULL,
generation INTEGER NOT NULL
) STRICT

View File

@@ -75,6 +75,10 @@ class TestPersistence extends SessionPersistence {
static snapshotOverride: (() => SessionPersistenceSnapshot[]) | undefined
static failure: unknown
locate(_meta: SessionHeader): undefined {
return undefined
}
static reset(entries: readonly { meta: SessionHeader; events: SessionEvent[] }[] = []): void {
this.entries = new Map()
this.revisions = new Map()
@@ -152,7 +156,9 @@ async function liveContext(config: ConstructorParameters<typeof SessionSearchSql
describe('SQLite session search', () => {
it('searches two-character Unicode61 tokens in live-only sessions', async () => {
const ctx = await liveContext({ path: ':memory:', snippetChars: 20 })
const session = ctx.sessions.create(SessionId('live'), { meta: { cwd: '/work', createdAt: 10, seedLength: 1 } })
const session = ctx.sessions.create(SessionId('live'), {
meta: { cwd: '/work', createdAt: 10, seedLength: 1, delegationDepth: 2 },
})
session.append(
'user/message',
{ content: [{ type: 'text', text: 'An AI helper' }], source: { kind: 'user' } },
@@ -171,7 +177,7 @@ describe('SQLite session search', () => {
const events: SessionEvent[] = [
{ type: 'user/message', seq: 0, time: 10, data: { content: [{ type: 'text', text: 'needle original' }], source: { kind: 'user' } }, surfaceOp: 'append' },
{ type: 'assistant/chunk', seq: 1, time: 11, data: { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'needle raw' } } },
{ type: 'user/message', seq: 2, time: 12, data: { content: [{ type: 'text', text: 'needle summary' }], source: { kind: 'plugin', plugin: 'test' } }, surfaceOp: { op: 'replace', start: 0, end: 0 } },
{ type: 'user/message', seq: 2, time: 12, data: { content: [{ type: 'text', text: 'needle summary' }], source: { kind: 'plugin', plugin: 'test' } }, surfaceOp: { op: 'replace', start: 0, end: 0 }, sourceEventSeqs: [0] },
{ type: 'turn/end', seq: 3, time: 13, data: { turn: 1, reason: { kind: 'error', step: 1, message: 'needle failure' } } },
]
ctx.sessions.create(SessionId('a'), { seed: events, meta: { cwd: '/a', parentSession: parent, createdAt: 20 } })
@@ -779,11 +785,14 @@ describe('SQLite reconciliation and source lifecycle', () => {
})
it('rejects immutable header conflicts between live and persisted sources', async () => {
const shared = header('conflict', 10)
const shared = header('conflict', 10, { delegationDepth: 1 })
TestPersistence.reset([{ meta: shared, events: messageEvents('persisted needle') }])
const ctx = await liveContext()
await ctx.plugin(TestPersistence)
ctx.sessions.create(shared.id, { seed: messageEvents('live needle'), meta: { createdAt: 11 } })
ctx.sessions.create(shared.id, {
seed: messageEvents('live needle'),
meta: { createdAt: 10, delegationDepth: 2 },
})
await expect(ctx.sessionSearch.searchSessions({ query: 'needle' }))
.rejects.toThrow(expectCode('SESSION_QUERY_SOURCE_CONFLICT'))

View File

@@ -23,6 +23,9 @@
{
"path": "../../session-persistence/session-persistence"
},
{
"path": "../../support/invariants"
},
{
"path": "../session-query"
}