refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session-query/README.md
README.md: 90b105d44a2affd8d487db20dc51357425446f8b
README.zh.md: 305223e77385f7ee640b54e1d03e8659967da43e
README.md: 75f447f667261fe3005d1a95e285120bebe6338d
README.zh.md: b89ca7441ed825c7d76e9fec4dd6c400c6c6811e

View File

@@ -8,7 +8,7 @@ This family provides authorized retrieval over live and durable session logs, in
|---|---|---|
| [`session-query/`](session-query/README.md) | Defines trusted reads, relationship queries, and search operations | `ctx.sessionQuery` |
| [`session-query-sqlite/`](session-query-sqlite/README.md) | Implements session queries with SQLite full-text search | `ctx.sessionQuery` |
| [`session-export/`](session-export/README.md) | Adds the Web `/export` command, shared browser download state, and result modal over the Host ZIP endpoint | `ctx.sessionExport` |
| [`session-log-download/`](session-log-download/README.md) | Adds the Web `/export` command, shared browser download state, and result modal over the Host ZIP endpoint | `ctx.sessionLogDownload` |
| [`tool-session-query/`](tool-session-query/README.md) | Exposes workspace-authorized session queries to the model | registers on `ctx.tools` |
The subsystem reference — logical records, bounded reads, traces, filters, result pages — is [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md).

View File

@@ -8,7 +8,7 @@
|---|---|---|
| [`session-query/`](session-query/README.md) | 定义可信读取、关系查询和搜索操作 | `ctx.sessionQuery` |
| [`session-query-sqlite/`](session-query-sqlite/README.md) | 使用 SQLite 全文搜索实现会话查询 | `ctx.sessionQuery` |
| [`session-export/`](session-export/README.md) | 在 Host ZIP 端点之上增加 Web `/export` 命令、共享浏览器下载状态和结果弹窗 | `ctx.sessionExport` |
| [`session-log-download/`](session-log-download/README.md) | 在 Host ZIP 端点之上增加 Web `/export` 命令、共享浏览器下载状态和结果弹窗 | `ctx.sessionLogDownload` |
| [`tool-session-query/`](tool-session-query/README.md) | 向模型公开经过工作区授权的会话查询 | 注册到 `ctx.tools` |
子系统参考——逻辑记录、有界读取、追踪、筛选器、结果页——见 [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md)。

View File

@@ -1,3 +0,0 @@
import { clientBundle } from '../../client/tsdown.client.ts'
export default clientBundle('@deepseek-ai/dsh-session-export', ['lib/types/index.js', 'lib/types/invariant.js'])

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session-query/session-export/README.md
README.md: 4abee799c51d50342421ff594e88b03a11e785fa
README.zh.md: 9b3ab129c132f797bbede8d8676bc8af954c1f90
# pnpm run verify-translation-pairing --write packages/session-query/session-log-download/README.md
README.md: eb8f26e4c44abfea14a555555ceec4ad1da48c18
README.zh.md: a1185315469518eb29acaa1a9c9010aa6b4660b4

View File

@@ -1,4 +1,4 @@
# @deepseek-ai/dsh-session-export
# @deepseek-ai/dsh-session-log-download
English | [中文](README.zh.md)
@@ -20,11 +20,11 @@ The modal reports preparation, download start, or failure. Closing it does not c
## Composition
```yaml
- id: session-export
name: '@deepseek-ai/dsh-session-export'
- id: session-log-download
name: '@deepseek-ai/dsh-session-log-download'
```
The Web bundle mounts the package beside `dsh-host-apiproxy`, `dsh-commands`, `dsh-client-ui-command`, and `dsh-client-ui-conversation`. The package contributes its button and modal to the right-aligned `conversation.session.header.utilities` list, independently of the title-adjacent mode, Subagent, and Task entries in `conversation.session.header.actions`; Trajectory carries no export control.
The Web bundle mounts the package beside `dsh-host-apiproxy`, `dsh-commands`, `dsh-client-ui-commands`, and `dsh-client-ui-conversation`. The package contributes its button and modal to the right-aligned `conversation.session.header.utilities` list, independently of the title-adjacent mode, Subagent, and Task entries in `conversation.session.header.actions`; Trajectory carries no export control.
## Model Experience

View File

@@ -1,4 +1,4 @@
# @deepseek-ai/dsh-session-export
# @deepseek-ai/dsh-session-log-download
[English](README.md) | 中文
@@ -20,11 +20,11 @@ Host 下载端点会在 `readRaw` 前 flush 活动的根 Session因此斜杠
## 组合
```yaml
- id: session-export
name: '@deepseek-ai/dsh-session-export'
- id: session-log-download
name: '@deepseek-ai/dsh-session-log-download'
```
Web bundle 将本包与 `dsh-host-apiproxy``dsh-commands``dsh-client-ui-command``dsh-client-ui-conversation` 一起挂载。本包把按钮和弹窗贡献到最右侧的 `conversation.session.header.utilities` 列表,与标题旁 `conversation.session.header.actions` 中的模式、Subagent 和 Task 配置项相互独立Trajectory 不包含导出入口。
Web bundle 将本包与 `dsh-host-apiproxy``dsh-commands``dsh-client-ui-commands``dsh-client-ui-conversation` 一起挂载。本包把按钮和弹窗贡献到最右侧的 `conversation.session.header.utilities` 列表,与标题旁 `conversation.session.header.actions` 中的模式、Subagent 和 Task 配置项相互独立Trajectory 不包含导出入口。
## 模型体验

View File

@@ -1,12 +1,12 @@
{
"name": "@deepseek-ai/dsh-session-export",
"name": "@deepseek-ai/dsh-session-log-download",
"description": "Web Session-log export command and shared download dialog",
"version": "0.0.1-rc.2",
"publishConfig": { "access": "restricted" },
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "packages/session-query/session-export"
"directory": "packages/session-query/session-log-download"
},
"type": "module",
"main": "lib/index.js",
@@ -25,7 +25,7 @@
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-client-locale": "workspace:^",
"@deepseek-ai/dsh-client-runtime": "workspace:^",
"@deepseek-ai/dsh-client-ui-command": "workspace:^",
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
@@ -39,7 +39,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-client-locale": "workspace:^",
"@deepseek-ai/dsh-client-runtime": "workspace:^",
"@deepseek-ai/dsh-client-ui-command": "workspace:^",
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
@@ -54,7 +54,7 @@
"inject": [
"@deepseek-ai/dsh-client-locale",
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-command",
"@deepseek-ai/dsh-client-ui-commands",
"@deepseek-ai/dsh-client-ui-conversation"
],
"platform": "web"

View File

@@ -1,30 +1,30 @@
import type { ObservableSnapshot, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import type { SessionExportDownloadState } from './controller.ts'
import type { SessionLogDownloadState } from './controller.ts'
import { NS } from './locales.ts'
/** Browser operations and state injected into the Session Header contribution. */
export interface SessionExportDialogInjected {
hooks: { sessionExport: ObservableSnapshot<SessionExportDownloadState> }
export interface SessionLogDownloadDialogInjected {
hooks: { sessionLogDownload: ObservableSnapshot<SessionLogDownloadState> }
request: (sessionId: SessionId) => Promise<void>
dismiss: (sessionId: SessionId) => void
}
export type SessionExportDialogProps =
export type SessionLogDownloadDialogProps =
PropsRuntime<'conversation.session.header.utilities'>
& PropsLocale<typeof NS>
& InjectFace<SessionExportDialogInjected>
& InjectFace<SessionLogDownloadDialogInjected>
/**
* Modal shared by the Session Header button and this browser's `/export` command.
* @param props - Session runtime, bound controller state, actions, and localized copy.
* @returns the modal portal contribution.
*/
export function SessionExportDialog({
sessionId, useSessionExport, dismiss, t,
}: SessionExportDialogProps) {
const entry = useSessionExport(state => state.bySession[String(sessionId)])
export function SessionLogDownloadDialog({
sessionId, useSessionLogDownload, dismiss, t,
}: SessionLogDownloadDialogProps) {
const entry = useSessionLogDownload(state => state.bySession[String(sessionId)])
const status = entry?.status
const open = entry?.open === true

View File

@@ -1,6 +1,6 @@
import type { ReactNode } from 'react'
import { IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
import { SessionExportDialog, type SessionExportDialogProps } from './Dialog.tsx'
import { SessionLogDownloadDialog, type SessionLogDownloadDialogProps } from './Dialog.tsx'
import css from './HeaderAction.module.css'
/**
@@ -8,9 +8,9 @@ import css from './HeaderAction.module.css'
* @param props - Session runtime, download controller, and localized dialog copy.
* @returns the persistent Header action and Session-scoped dialog.
*/
export function SessionExportHeader(props: SessionExportDialogProps): ReactNode {
const { sessionId, useSessionExport, request } = props
const entry = useSessionExport(state => state.bySession[String(sessionId)])
export function SessionLogDownloadHeaderAction(props: SessionLogDownloadDialogProps): ReactNode {
const { sessionId, useSessionLogDownload, request } = props
const entry = useSessionLogDownload(state => state.bySession[String(sessionId)])
const busy = entry?.status === 'downloading'
return (
@@ -25,7 +25,7 @@ export function SessionExportHeader(props: SessionExportDialogProps): ReactNode
<span>Session log</span>
<IconDownloadOutline16 size={12} />
</button>
<SessionExportDialog {...props} />
<SessionLogDownloadDialog {...props} />
</>
)
}

View File

@@ -3,24 +3,24 @@
import { createSnapshotStore, type SessionId, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
/** Download phases presented by the shared modal. */
export type SessionExportDownloadStatus = 'downloading' | 'success' | 'error'
export type SessionLogDownloadStatus = 'downloading' | 'success' | 'error'
/** One Session's current download-dialog state. */
export interface SessionExportDownloadEntry {
export interface SessionLogDownloadEntry {
readonly open: boolean
readonly status: SessionExportDownloadStatus
readonly status: SessionLogDownloadStatus
readonly error: string | null
}
/** Download states keyed by the Session whose Header owns the dialog. */
export interface SessionExportDownloadState {
bySession: Record<string, SessionExportDownloadEntry | undefined>
export interface SessionLogDownloadState {
bySession: Record<string, SessionLogDownloadEntry | undefined>
}
type Fetch = (input: string | URL, init?: RequestInit) => Promise<Response>
type Save = (url: string, filename: string) => void
const INITIAL: SessionExportDownloadState = { bySession: {} }
const INITIAL: SessionLogDownloadState = { bySession: {} }
/**
* Collapse an untrusted Session id into the filename convention owned by the host endpoint.
@@ -54,9 +54,9 @@ function messageOf(error: unknown): string {
}
/** Owns one in-flight browser download per Session and publishes modal state. */
export class SessionExportDownloadController {
export class SessionLogDownloadController {
/** uSES-safe state source shared by every Session-scoped modal contribution. */
readonly store: SnapshotStore<SessionExportDownloadState> = createSnapshotStore(INITIAL)
readonly store: SnapshotStore<SessionLogDownloadState> = createSnapshotStore(INITIAL)
private readonly active = new Map<SessionId, { readonly abort: AbortController; readonly done: Promise<void> }>()
private disposed = false
@@ -129,7 +129,7 @@ export class SessionExportDownloadController {
}
}
private publish(sessionId: SessionId, entry: SessionExportDownloadEntry): void {
private publish(sessionId: SessionId, entry: SessionLogDownloadEntry): void {
this.store.update((state) => {
state.bySession = { ...state.bySession, [String(sessionId)]: entry }
})

View File

@@ -2,26 +2,26 @@
import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import type {} from '@deepseek-ai/dsh-client-locale/client'
import type {} from '@deepseek-ai/dsh-client-ui-command/client'
import type {} from '@deepseek-ai/dsh-client-ui-commands/client'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
import { SessionExportDownloadController } from './controller.ts'
import type { SessionExportDialogInjected } from './Dialog.tsx'
import { SessionExportHeader } from './HeaderAction.tsx'
import { en, NS, zh, type SessionExportKey } from './locales.ts'
import { SessionLogDownloadController } from './controller.ts'
import type { SessionLogDownloadDialogInjected } from './Dialog.tsx'
import { SessionLogDownloadHeaderAction } from './HeaderAction.tsx'
import { en, NS, zh, type SessionLogDownloadKey } from './locales.ts'
declare module '@deepseek-ai/cordis' {
interface Context {
sessionExport: SessionExportDownloadController
sessionLogDownload: SessionLogDownloadController
}
}
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
'session-export': SessionExportKey
'session-log-download': SessionLogDownloadKey
}
}
export type { SessionExportDownloadEntry, SessionExportDownloadState } from './controller.ts'
export type { SessionLogDownloadEntry, SessionLogDownloadState } from './controller.ts'
export const inject = ['slots', 'locale']
@@ -30,23 +30,23 @@ export const inject = ['slots', 'locale']
* @param ctx - browser context carrying slots and locale services.
*/
export function apply(ctx: ClientContext): void {
const controller = new SessionExportDownloadController()
ctx.provide('sessionExport', controller)
ctx.effect(() => async () => { await controller.dispose() }, 'session-export: browser download lifecycle')
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'session-export: browser dictionaries')
const controller = new SessionLogDownloadController()
ctx.provide('sessionLogDownload', controller)
ctx.effect(() => async () => { await controller.dispose() }, 'session-log-download: browser download lifecycle')
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'session-log-download: browser dictionaries')
ctx.on('command/executed', (sessionId, commandName, result) => {
if (commandName === 'export' && result.kind === 'success') void controller.download(sessionId)
})
ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
name: 'conversation.session.header.utilities',
id: 'session-export',
id: 'session-log-download',
locale: NS,
inject: (): SessionExportDialogInjected => ({
hooks: { sessionExport: controller.store },
inject: (): SessionLogDownloadDialogInjected => ({
hooks: { sessionLogDownload: controller.store },
request: (sessionId: SessionId) => controller.download(sessionId),
dismiss: (sessionId: SessionId) => { controller.dismiss(sessionId) },
}),
}, SessionExportHeader))
}, SessionLogDownloadHeaderAction))
}
export type { SessionExportDialogInjected, SessionExportDialogProps } from './Dialog.tsx'
export type { SessionLogDownloadDialogInjected, SessionLogDownloadDialogProps } from './Dialog.tsx'

View File

@@ -1,5 +1,5 @@
/** Locale namespace owned by Session export browser feedback. */
export const NS = 'session-export'
export const NS = 'session-log-download'
/** Simplified-Chinese Session export strings. */
export const zh = {
@@ -24,4 +24,4 @@ export const en: Record<keyof typeof zh, string> = {
}
/** Stable locale keys consumed by the shared modal. */
export type SessionExportKey = keyof typeof zh
export type SessionLogDownloadKey = keyof typeof zh

View File

@@ -3,7 +3,7 @@
import type { Context } from '@deepseek-ai/cordis'
import type { CommandResult } from '@deepseek-ai/dsh-commands'
export const name = 'session-export'
export const name = 'session-log-download'
export const inject = ['commands']
const REQUESTED: CommandResult = {
@@ -22,5 +22,5 @@ export function apply(ctx: Context): void {
handler: invocation => Promise.resolve(invocation.rawInput.trim() === ''
? REQUESTED
: { kind: 'error', text: 'The Web /export command does not accept a path.' }),
}), 'session-export: command')
}), 'session-log-download: command')
}

View File

@@ -1,10 +1,10 @@
/** Package invariant companion for `@deepseek-ai/dsh-session-export`. */
/** Package invariant companion for `@deepseek-ai/dsh-session-log-download`. */
/* jscpd:ignore-start */
import type { Context } from '@deepseek-ai/cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-session-export'
const PACKAGE_NAME = '@deepseek-ai/dsh-session-log-download'
export const name = 'session-export-invariant'
export const inject = ['invariants']

View File

@@ -1,17 +1,17 @@
import { Context } from '@deepseek-ai/cordis'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
import { SlotRegistry } from '@deepseek-ai/dsh-client-runtime/client'
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
import { SessionExportHeader } from '../src/client/HeaderAction.tsx'
import { SessionLogDownloadHeaderAction } from '../src/client/HeaderAction.tsx'
import { apply, inject } from '../src/client/index.ts'
const SID = 'session-export-apply' as SessionId
afterEach(() => { vi.unstubAllGlobals() })
function declare(slots: SlotsService): () => void {
function declare(slots: SlotRegistry): () => void {
return slots.register({
name: 'root',
children: {
@@ -23,30 +23,30 @@ function declare(slots: SlotsService): () => void {
async function bench() {
const ctx = new Context()
await ctx.plugin(SlotsService).await()
const slots = ctx.get('slots') as SlotsService
await ctx.plugin(SlotRegistry).await()
const slots = ctx.get('slots') as SlotRegistry
const declaration = declare(slots)
ctx.provide('locale', new LocaleService(ctx))
ctx.provide('locale', new LocaleRuntime(ctx))
const fiber = ctx.plugin({ inject: [...inject], apply })
await fiber.await()
return { ctx, slots, declaration, fiber }
}
describe('session-export browser plugin', () => {
describe('session-log-download browser plugin', () => {
it('provides one controller and removes its Header contribution on disposal', async () => {
vi.stubGlobal('fetch', vi.fn(async () => new Response('', { status: 500 })))
const b = await bench()
expect(inject).toEqual(['slots', 'locale'])
expect(b.ctx.sessionExport).toBeDefined()
expect(b.ctx.sessionLogDownload).toBeDefined()
expect(b.slots.entries('conversation.session.header.actions')).toHaveLength(0)
const entry = b.slots.entries('conversation.session.header.utilities')[0]
expect(entry?.component).toBe(SessionExportHeader)
expect(entry?.options).toMatchObject({ id: 'session-export' })
const injected = (entry?.inject as unknown as () => import('../src/client/Dialog.tsx').SessionExportDialogInjected)()
expect(entry?.component).toBe(SessionLogDownloadHeaderAction)
expect(entry?.options).toMatchObject({ id: 'session-log-download' })
const injected = (entry?.inject as unknown as () => import('../src/client/Dialog.tsx').SessionLogDownloadDialogInjected)()
await injected.request(SID)
expect(b.ctx.sessionExport.store.getSnapshot().bySession[SID]?.status).toBe('error')
expect(b.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.status).toBe('error')
injected.dismiss(SID)
expect(b.ctx.sessionExport.store.getSnapshot().bySession[SID]?.open).toBe(false)
expect(b.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.open).toBe(false)
await b.fiber.dispose()
expect(b.slots.entries('conversation.session.header.utilities')).toHaveLength(0)
@@ -65,9 +65,9 @@ describe('session-export browser plugin', () => {
first.ctx.emit('command/executed', SID, 'export', { kind: 'success' })
await vi.waitFor(() => {
expect(fetcher).toHaveBeenCalledOnce()
expect(first.ctx.sessionExport.store.getSnapshot().bySession[SID]?.status).toBe('error')
expect(first.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.status).toBe('error')
})
expect(second.ctx.sessionExport.store.getSnapshot().bySession[SID]).toBeUndefined()
expect(second.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]).toBeUndefined()
await first.fiber.dispose()
await second.fiber.dispose()
@@ -79,7 +79,7 @@ describe('session-export browser plugin', () => {
expect(b.slots.entries('conversation.session.header.utilities')).toHaveLength(0)
const redeclare = declare(b.slots)
await Promise.resolve()
expect(b.slots.entries('conversation.session.header.utilities')[0]?.component).toBe(SessionExportHeader)
expect(b.slots.entries('conversation.session.header.utilities')[0]?.component).toBe(SessionLogDownloadHeaderAction)
redeclare()
await b.fiber.dispose()
})

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import type { CommandDefinition, CommandInvocation } from '@deepseek-ai/dsh-commands'
import * as SessionExport from '../src/index.ts'
import * as SessionLogDownload from '../src/index.ts'
describe('/export Web download command', () => {
it('registers one pathless command and removes it with the plugin fiber', async () => {
@@ -13,7 +13,7 @@ describe('/export Web download command', () => {
return () => { descriptor = undefined }
},
} as never)
const fiber = await ctx.plugin(SessionExport)
const fiber = await ctx.plugin(SessionLogDownload)
expect(descriptor).toMatchObject({
name: 'export',

View File

@@ -2,7 +2,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import {
downloadUrl, SessionExportDownloadController, sessionLogZipFilename,
downloadUrl, SessionLogDownloadController, sessionLogZipFilename,
} from '../src/client/controller.ts'
const SID = 'session-export-controller' as SessionId
@@ -12,11 +12,11 @@ afterEach(() => {
vi.unstubAllGlobals()
})
describe('SessionExportDownloadController', () => {
describe('SessionLogDownloadController', () => {
it('downloads the host ZIP and publishes one shared success state', async () => {
const fetcher = vi.fn(async () => new Response('zip', { status: 200 }))
const save = vi.fn()
const controller = new SessionExportDownloadController(fetcher, save)
const controller = new SessionLogDownloadController(fetcher, save)
await controller.download(SID)
@@ -39,7 +39,7 @@ describe('SessionExportDownloadController', () => {
it('collapses concurrent gestures and preserves a dismissed dialog', async () => {
const response = Promise.withResolvers<Response>()
const fetcher = vi.fn(() => response.promise)
const controller = new SessionExportDownloadController(fetcher, vi.fn())
const controller = new SessionLogDownloadController(fetcher, vi.fn())
const first = controller.download(SID)
const second = controller.download(SID)
@@ -54,7 +54,7 @@ describe('SessionExportDownloadController', () => {
})
it('publishes HTTP and transport failures without leaking rejections', async () => {
const http = new SessionExportDownloadController(
const http = new SessionLogDownloadController(
async () => new Response('backend unavailable', { status: 500 }), vi.fn(),
)
await http.download(SID)
@@ -64,13 +64,13 @@ describe('SessionExportDownloadController', () => {
error: 'Export failed: HTTP 500 backend unavailable',
})
const transport = new SessionExportDownloadController(async () => { throw 'offline' }, vi.fn())
const transport = new SessionLogDownloadController(async () => { throw 'offline' }, vi.fn())
await transport.download(SID)
expect(transport.store.getSnapshot().bySession[SID]?.error).toBe('offline')
transport.dismiss('absent' as SessionId)
const emptyDetail = new SessionExportDownloadController(
const emptyDetail = new SessionLogDownloadController(
async () => ({
ok: false, status: 503, text: async () => { throw new Error('body unavailable') },
}) as unknown as Response,
@@ -88,7 +88,7 @@ describe('SessionExportDownloadController', () => {
reject(signal?.reason instanceof Error ? signal.reason : new Error('aborted'))
}, { once: true })
}))
const controller = new SessionExportDownloadController(fetcher, vi.fn())
const controller = new SessionLogDownloadController(fetcher, vi.fn())
const pending = controller.download(SID)
await controller.dispose()
@@ -104,7 +104,7 @@ describe('SessionExportDownloadController', () => {
const fetcher = vi.fn(async (_input: string | URL, _init?: RequestInit) => new Response('zip'))
vi.stubGlobal('fetch', fetcher)
const click = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
const controller = new SessionExportDownloadController()
const controller = new SessionLogDownloadController()
await controller.download(SID)
@@ -115,7 +115,7 @@ describe('SessionExportDownloadController', () => {
it('defaults dialog openness when state is externally cleared before settlement', async () => {
const success = Promise.withResolvers<Response>()
const successful = new SessionExportDownloadController(() => success.promise, vi.fn())
const successful = new SessionLogDownloadController(() => success.promise, vi.fn())
const successRun = successful.download(SID)
successful.store.set({ bySession: {} })
success.resolve(new Response('zip'))
@@ -123,7 +123,7 @@ describe('SessionExportDownloadController', () => {
expect(successful.store.getSnapshot().bySession[SID]?.open).toBe(true)
const failure = Promise.withResolvers<Response>()
const failing = new SessionExportDownloadController(() => failure.promise, vi.fn())
const failing = new SessionLogDownloadController(() => failure.promise, vi.fn())
const failureRun = failing.download(SID)
failing.store.set({ bySession: {} })
failure.reject(new Error('failed after clear'))

View File

@@ -3,34 +3,34 @@ import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useSyncExternalStore } from 'react'
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import { SessionExportDownloadController } from '../src/client/controller.ts'
import { SessionExportDialog } from '../src/client/Dialog.tsx'
import type { SessionExportDialogProps } from '../src/client/Dialog.tsx'
import { SessionLogDownloadController } from '../src/client/controller.ts'
import { SessionLogDownloadDialog } from '../src/client/Dialog.tsx'
import type { SessionLogDownloadDialogProps } from '../src/client/Dialog.tsx'
import { en } from '../src/client/locales.ts'
const SID = 'session-export-dialog' as SessionId
function bench(
controller = new SessionExportDownloadController(
controller = new SessionLogDownloadController(
async () => new Response('zip', { status: 200 }), vi.fn(),
),
) {
const dismiss = vi.fn((sessionId: SessionId) => { controller.dismiss(sessionId) })
function useSessionExport<T>(selector: (state: ReturnType<typeof controller.store.getSnapshot>) => T): T {
function useSessionLogDownload<T>(selector: (state: ReturnType<typeof controller.store.getSnapshot>) => T): T {
return useSyncExternalStore(
listener => controller.store.subscribe(listener),
() => selector(controller.store.getSnapshot()),
)
}
const t = (key: keyof typeof en): string => en[key]
const props = { sessionId: SID, useSessionExport, dismiss, t } as unknown as SessionExportDialogProps
const view = render(<SessionExportDialog {...props} />)
const props = { sessionId: SID, useSessionLogDownload, dismiss, t } as unknown as SessionLogDownloadDialogProps
const view = render(<SessionLogDownloadDialog {...props} />)
return { controller, dismiss, view }
}
afterEach(cleanup)
describe('SessionExportDialog', () => {
describe('SessionLogDownloadDialog', () => {
it('shows a controller failure and closes it without reading Session history', async () => {
const b = bench()
act(() => {
@@ -49,7 +49,7 @@ describe('SessionExportDialog', () => {
it('renders the in-flight state and the settled browser download state', async () => {
let release!: (response: Response) => void
const pending = new Promise<Response>((resolve) => { release = resolve })
const controller = new SessionExportDownloadController(() => pending, vi.fn())
const controller = new SessionLogDownloadController(() => pending, vi.fn())
const b = bench(controller)
const download = controller.download(SID)

View File

@@ -3,15 +3,15 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useSyncExternalStore } from 'react'
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import { SessionExportDownloadController } from '../src/client/controller.ts'
import { SessionExportHeader } from '../src/client/HeaderAction.tsx'
import type { SessionExportDialogProps } from '../src/client/Dialog.tsx'
import { SessionLogDownloadController } from '../src/client/controller.ts'
import { SessionLogDownloadHeaderAction } from '../src/client/HeaderAction.tsx'
import type { SessionLogDownloadDialogProps } from '../src/client/Dialog.tsx'
import { en } from '../src/client/locales.ts'
const SID = 'session-export-header' as SessionId
function bindSessionExport(controller: SessionExportDownloadController) {
return function useSessionExport<T>(selector: (state: ReturnType<typeof controller.store.getSnapshot>) => T): T {
function bindSessionExport(controller: SessionLogDownloadController) {
return function useSessionLogDownload<T>(selector: (state: ReturnType<typeof controller.store.getSnapshot>) => T): T {
return useSyncExternalStore(
listener => controller.store.subscribe(listener),
() => selector(controller.store.getSnapshot()),
@@ -20,18 +20,18 @@ function bindSessionExport(controller: SessionExportDownloadController) {
}
function bench() {
const controller = new SessionExportDownloadController(async () => new Response('zip'), vi.fn())
const controller = new SessionLogDownloadController(async () => new Response('zip'), vi.fn())
const request = vi.fn((sessionId: SessionId) => controller.download(sessionId))
const dismiss = vi.fn((sessionId: SessionId) => { controller.dismiss(sessionId) })
const useSessionExport = bindSessionExport(controller)
const useSessionLogDownload = bindSessionExport(controller)
const props = {
sessionId: SID,
useSessionExport,
useSessionLogDownload,
request,
dismiss,
t: (key: keyof typeof en): string => en[key],
} as unknown as SessionExportDialogProps
const view = render(<SessionExportHeader {...props} />)
} as unknown as SessionLogDownloadDialogProps
const view = render(<SessionLogDownloadHeaderAction {...props} />)
return { controller, request, view }
}
@@ -51,15 +51,15 @@ describe('Session export Header action', () => {
const b = bench()
let release!: (response: Response) => void
const pending = new Promise<Response>((resolve) => { release = resolve })
const controller = new SessionExportDownloadController(() => pending, vi.fn())
const useSessionExport = bindSessionExport(controller)
b.view.rerender(<SessionExportHeader {...({
const controller = new SessionLogDownloadController(() => pending, vi.fn())
const useSessionLogDownload = bindSessionExport(controller)
b.view.rerender(<SessionLogDownloadHeaderAction {...({
sessionId: SID,
useSessionExport,
useSessionLogDownload,
request: (sessionId: SessionId) => controller.download(sessionId),
dismiss: (sessionId: SessionId) => { controller.dismiss(sessionId) },
t: (key: keyof typeof en): string => en[key],
} as unknown as SessionExportDialogProps)} />)
} as unknown as SessionLogDownloadDialogProps)} />)
const download = controller.download(SID)
const button = b.view.getByRole('button', { name: 'Session log' })

View File

@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { apply, inject, name } from '../src/invariant.ts'
describe('@deepseek-ai/dsh-session-export/invariant', () => {
describe('@deepseek-ai/dsh-session-log-download/invariant', () => {
it('registers the package-owned empty companion', async () => {
const register = vi.fn(() => vi.fn())
const ctx = new Context()
@@ -10,7 +10,7 @@ describe('@deepseek-ai/dsh-session-export/invariant', () => {
const dispose = await apply(ctx)
expect(name).toBe('session-export-invariant')
expect(inject).toEqual(['invariants'])
expect(register).toHaveBeenCalledWith('@deepseek-ai/dsh-session-export', expect.any(Function))
expect(register).toHaveBeenCalledWith('@deepseek-ai/dsh-session-log-download', expect.any(Function))
dispose()
})
})

View File

@@ -7,9 +7,9 @@ import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import Include from '@deepseek-ai/cordis-plugin-include'
import type { Agent } from '@deepseek-ai/dsh-agent'
import CommandService from '@deepseek-ai/dsh-commands'
import CommandRuntime from '@deepseek-ai/dsh-commands'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import * as SessionExport from '@deepseek-ai/dsh-session-export'
import * as SessionLogDownload from '@deepseek-ai/dsh-session-log-download'
let root: string | undefined
let context: Context | undefined
@@ -21,14 +21,14 @@ afterEach(async () => {
root = undefined
})
describe('session-export real Loader composition', () => {
describe('session-log-download real Loader composition', () => {
it('discovers and executes /export through the assembled command plane', async () => {
root = await mkdtemp(join(tmpdir(), 'dsh-session-export-loader-'))
const configPath = join(root, 'cordis.yml')
await writeFile(configPath, [
"- name: '@deepseek-ai/dsh-session'",
"- name: '@deepseek-ai/dsh-commands'",
"- name: '@deepseek-ai/dsh-session-export'",
"- name: '@deepseek-ai/dsh-session-log-download'",
'',
].join('\n'))
@@ -38,8 +38,8 @@ describe('session-export real Loader composition', () => {
context.loader.builtins.include = Include
const modules = new Map<string, unknown>([
['@deepseek-ai/dsh-session', SessionStore],
['@deepseek-ai/dsh-commands', CommandService],
['@deepseek-ai/dsh-session-export', SessionExport],
['@deepseek-ai/dsh-commands', CommandRuntime],
['@deepseek-ai/dsh-session-log-download', SessionLogDownload],
])
context.loader.internal = {
version: 'v2',

View File

@@ -12,10 +12,10 @@
{ "path": "../../interaction/commands" },
{ "path": "../../client/locale" },
{ "path": "../../client/runtime" },
{ "path": "../../client/ui-command" },
{ "path": "../../client/ui-commands" },
{ "path": "../../client/ui-conversation" },
{ "path": "../../client/ui-primitives" },
{ "path": "../../client/ui-slots" },
{ "path": "../../support/invariants" }
{ "path": "../../runtime-diagnostics/invariants" }
]
}

View File

@@ -0,0 +1,3 @@
import { clientBundle } from '../../client/tsdown.client.ts'
export default clientBundle('@deepseek-ai/dsh-session-log-download', ['lib/types/index.js', 'lib/types/invariant.js'])

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session-query/session-query-sqlite/README.md
README.md: 8784f1b2c2a3c9291b761c0a6e68201286b9e306
README.zh.md: 14c31f3d700ccc0ac7fd6e2837afa0bce50eb20e
README.md: 846f79bd8383a8114a6d1329e7dbe931a67d98d0
README.zh.md: e84e521dd5f392dbcede1e1dd957e4ce0a9bc50f

View File

@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
Concrete `ctx.sessionQuery` provider. `SessionQuerySqlite` inherits exact reads, traces, and provider-independent filters from the Service Definition package and implements its two full-text methods with SQLite FTS5. Search uses the live-preferred logical session corpus and groups cross-session results by their strongest event.
Concrete `ctx.sessionQuery` provider. `SqliteSessionQueryEngine` inherits exact reads, traces, and provider-independent filters from the Service Definition package and implements its two full-text methods with SQLite FTS5. Search uses the live-preferred logical session corpus and groups cross-session results by their strongest event.
## Search contract

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
具体 `ctx.sessionQuery` 提供方。`SessionQuerySqlite` 从 Service Definition 包继承精确读取、跟踪和提供方无关的过滤,并使用 SQLite FTS5 实现其两个全文方法。搜索使用实时优先的逻辑会话语料库,并按每个会话中匹配度最高的事件对跨会话结果分组。
具体 `ctx.sessionQuery` 提供方。`SqliteSessionQueryEngine` 从 Service Definition 包继承精确读取、跟踪和提供方无关的过滤,并使用 SQLite FTS5 实现其两个全文方法。搜索使用实时优先的逻辑会话语料库,并按每个会话中匹配度最高的事件对跨会话结果分组。
## 搜索约定

View File

@@ -14,7 +14,7 @@ import type {
SessionPersistenceRevision,
SessionPersistenceSnapshot,
} from '@deepseek-ai/dsh-session-persistence'
import SessionQueryService, {
import SessionQueryEngine, {
SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
SESSION_QUERY_READ_WINDOW_MAX,
SessionQueryError,
@@ -187,7 +187,7 @@ interface CursorPayload {
}
/** Concrete SQLite owner of the combined `ctx.sessionQuery` service. */
export class SessionQuerySqlite extends SessionQueryService {
export class SqliteSessionQueryEngine extends SessionQueryEngine {
static override inject = ['sessions']
static Config: z<Config> = z.object({
@@ -1079,4 +1079,4 @@ function isRuntimeArray(value: unknown): boolean {
return Array.isArray(value)
}
export default SessionQuerySqlite
export default SqliteSessionQueryEngine

View File

@@ -10,8 +10,8 @@ import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
import SessionStore from '@deepseek-ai/dsh-session'
import SessionPersistenceSqlite from '@deepseek-ai/dsh-session-persistence-sqlite'
import SessionQuerySqlite, * as queryModule from '@deepseek-ai/dsh-session-query-sqlite'
import SqliteSessionPersistence from '@deepseek-ai/dsh-session-persistence-sqlite'
import SqliteSessionQueryEngine, * as queryModule from '@deepseek-ai/dsh-session-query-sqlite'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -36,11 +36,11 @@ describe('dsh-session-query-sqlite real Loader path', () => {
const searchPath = await temporaryPath('derived.db')
const ctx = new Context()
await ctx.plugin(SessionStore)
const persistence = await ctx.plugin(SessionPersistenceSqlite, { path: persistencePath })
const persistence = await ctx.plugin(SqliteSessionPersistence, { path: persistencePath })
const loader = Object.create(Loader.prototype) as Loader
const unwrapped = loader.unwrapExports(queryModule) as Parameters<Context['plugin']>[0]
expect(unwrapped).toBe(SessionQuerySqlite)
expect(unwrapped).toBe(SqliteSessionQueryEngine)
const query = await ctx.plugin(unwrapped, { path: searchPath })
const id = SessionId('loader-path')

View File

@@ -9,8 +9,8 @@ import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/ds
import type { SessionEvent, SessionHeader, SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import SessionPersistence, { SessionPersistenceRevision } from '@deepseek-ai/dsh-session-persistence'
import type { SessionPersistenceSnapshot } from '@deepseek-ai/dsh-session-persistence'
import SessionPersistenceSqlite from '@deepseek-ai/dsh-session-persistence-sqlite'
import SessionQuerySqlite, {
import SqliteSessionPersistence from '@deepseek-ai/dsh-session-persistence-sqlite'
import SqliteSessionQueryEngine, {
SESSION_QUERY_SQLITE_SCHEMA_VERSION,
} from '@deepseek-ai/dsh-session-query-sqlite'
import {
@@ -179,22 +179,22 @@ class TestPersistence extends SessionPersistence {
}
}
async function liveContext(config: ConstructorParameters<typeof SessionQuerySqlite>[1] = { path: ':memory:' }): Promise<Context> {
async function liveContext(config: ConstructorParameters<typeof SqliteSessionQueryEngine>[1] = { path: ':memory:' }): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQuerySqlite, config)
await ctx.plugin(SqliteSessionQueryEngine, config)
return ctx
}
describe('SQLite session search', () => {
it('defaults and validates opening policy and persisted inspection concurrency through its Cordis config', async () => {
const defaultCtx = await liveContext()
expect((defaultCtx.sessionQuery as SessionQuerySqlite).config.openAt).toBe('startup')
expect((defaultCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
expect((defaultCtx.sessionQuery as SqliteSessionQueryEngine).config.openAt).toBe('startup')
expect((defaultCtx.sessionQuery as SqliteSessionQueryEngine).config.persistedInspectConcurrency)
.toBe(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY)
const configuredValue = 2
const configured = new SessionQuerySqlite.Config({
const configured = new SqliteSessionQueryEngine.Config({
path: ':memory:',
openAt: 'first-search',
persistedInspectConcurrency: configuredValue,
@@ -202,16 +202,16 @@ describe('SQLite session search', () => {
expect(configured.openAt).toBe('first-search')
expect(configured.persistedInspectConcurrency).toBe(configuredValue)
const configuredCtx = await liveContext(configured)
expect((configuredCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
expect((configuredCtx.sessionQuery as SqliteSessionQueryEngine).config.persistedInspectConcurrency)
.toBe(configuredValue)
for (const persistedInspectConcurrency of [0, Number.MAX_SAFE_INTEGER + 1]) {
expect(() => new SessionQuerySqlite.Config({
expect(() => new SqliteSessionQueryEngine.Config({
path: ':memory:',
persistedInspectConcurrency,
})).toThrow()
}
expect(() => new SessionQuerySqlite.Config({
expect(() => new SqliteSessionQueryEngine.Config({
path: ':memory:',
openAt: 'later' as never,
})).toThrow()
@@ -221,7 +221,7 @@ describe('SQLite session search', () => {
const path = await temporaryPath('unopened.db')
const ctx = new Context()
await ctx.plugin(SessionStore)
const search = await ctx.plugin(SessionQuerySqlite, {
const search = await ctx.plugin(SqliteSessionQueryEngine, {
path,
openAt: 'first-search',
})
@@ -234,11 +234,11 @@ describe('SQLite session search', () => {
it('opens once on the first search and reuses readiness for later searches', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQuerySqlite, {
await ctx.plugin(SqliteSessionQueryEngine, {
path: ':memory:',
openAt: 'first-search',
})
const service = ctx.sessionQuery as SessionQuerySqlite
const service = ctx.sessionQuery as SqliteSessionQueryEngine
const internals = service as unknown as { _open(): Promise<void> }
const open = vi.spyOn(internals, '_open')
@@ -251,11 +251,11 @@ describe('SQLite session search', () => {
it('shares one readiness promise across concurrent first searches', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQuerySqlite, {
await ctx.plugin(SqliteSessionQueryEngine, {
path: ':memory:',
openAt: 'first-search',
})
const service = ctx.sessionQuery as SessionQuerySqlite
const service = ctx.sessionQuery as SqliteSessionQueryEngine
const internals = service as unknown as { _open(): Promise<void> }
const originalOpen = internals._open.bind(internals)
const release = Promise.withResolvers<undefined>()
@@ -647,7 +647,7 @@ describe('SQLite session search', () => {
]) {
const direct = new Context()
await direct.plugin(SessionStore)
expect(() => new SessionQuerySqlite(direct, config as never))
expect(() => new SqliteSessionQueryEngine(direct, config as never))
.toThrow(expectCode('SESSION_QUERY_INVALID_CONFIG'))
expect(direct.sessionQuery).toBeUndefined()
}
@@ -1068,7 +1068,7 @@ describe('SQLite reconciliation and source lifecycle', () => {
const first = new Context()
await first.plugin(SessionStore)
const firstPersistence = await first.plugin(TestPersistence)
const firstSearch = await first.plugin(SessionQuerySqlite, { path })
const firstSearch = await first.plugin(SqliteSessionQueryEngine, { path })
await first.sessionQuery.searchSessions({ query: 'needle' })
expect(Object.fromEntries(TestPersistence.inspections)).toEqual({ unchanged: 1, changed: 1, deleted: 1 })
await first.sessionQuery.searchSessions({ query: 'needle' })
@@ -1088,7 +1088,7 @@ describe('SQLite reconciliation and source lifecycle', () => {
const second = new Context()
await second.plugin(SessionStore)
const secondPersistence = await second.plugin(TestPersistence)
const secondSearch = await second.plugin(SessionQuerySqlite, { path })
const secondSearch = await second.plugin(SqliteSessionQueryEngine, { path })
const result = await second.sessionQuery.searchSessions({ query: 'needle' })
expect(result.items.map(item => item.header.id).sort()).toEqual([added.id, changed.id, unchanged.id].sort())
expect(Object.fromEntries(TestPersistence.inspections)).toEqual({
@@ -1118,7 +1118,7 @@ describe('SQLite reconciliation and source lifecycle', () => {
await first.plugin(SessionStore)
const persistence = await first.plugin(TestPersistence)
const live = first.sessions.create(shared.id, { seed: messageEvents('live needle'), meta: { createdAt: 10 } })
const search = await first.plugin(SessionQuerySqlite, { path })
const search = await first.plugin(SqliteSessionQueryEngine, { path })
await expect(first.sessionQuery.searchEvents({ sessionId: live.id, query: 'live' })).resolves.toMatchObject({ items: [{}] })
await search.dispose()
await persistence.dispose()
@@ -1126,7 +1126,7 @@ describe('SQLite reconciliation and source lifecycle', () => {
const second = new Context()
await second.plugin(SessionStore)
const persistenceAgain = await second.plugin(TestPersistence)
const searchAgain = await second.plugin(SessionQuerySqlite, { path })
const searchAgain = await second.plugin(SqliteSessionQueryEngine, { path })
await expect(second.sessionQuery.searchSessions({ query: 'live' })).resolves.toEqual({ items: [] })
await expect(second.sessionQuery.searchSessions({ query: 'persisted' }))
.resolves.toMatchObject({ items: [{ header: shared, live: false, persisted: true }] })
@@ -1202,7 +1202,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
expect((await stat(path)).mode & 0o777).toBe(0o600)
expect((await stat(`${path}-wal`)).mode & 0o777).toBe(0o600)
expect((await stat(`${path}-shm`)).mode & 0o777).toBe(0o600)
await (ctx.sessionQuery as SessionQuerySqlite).close()
await (ctx.sessionQuery as SqliteSessionQueryEngine).close()
})
it('creates a persistent rollback journal owner-only', async () => {
@@ -1213,7 +1213,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
expect((await stat(path)).mode & 0o777).toBe(0o600)
expect((await stat(`${path}-journal`)).mode & 0o777).toBe(0o600)
await (ctx.sessionQuery as SessionQuerySqlite).close()
await (ctx.sessionQuery as SqliteSessionQueryEngine).close()
})
it('preserves the mode of an existing database file', async () => {
@@ -1226,7 +1226,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
await ctx.sessionQuery.searchSessions({ query: 'needle' })
expect((await stat(path)).mode & 0o777).toBe(0o644)
await (ctx.sessionQuery as SessionQuerySqlite).close()
await (ctx.sessionQuery as SqliteSessionQueryEngine).close()
})
it('surfaces filesystem failures while pre-creating the database', async () => {
@@ -1234,7 +1234,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const ctx = new Context()
await ctx.plugin(SessionStore)
await expect(ctx.plugin(SessionQuerySqlite, { path })).rejects.toMatchObject({
await expect(ctx.plugin(SqliteSessionQueryEngine, { path })).rejects.toMatchObject({
code: 'SESSION_QUERY_INDEX_FAILED',
cause: { code: 'ERR_INVALID_ARG_VALUE' },
})
@@ -1244,14 +1244,14 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
it('resets a recognized incompatible schema but refuses unknown or foreign tables', { timeout: 20_000 }, async () => {
const stalePath = await temporaryPath('stale.db')
const staleOwner = await liveContext({ path: stalePath })
await (staleOwner.sessionQuery as SessionQuerySqlite).close()
await (staleOwner.sessionQuery as SqliteSessionQueryEngine).close()
const stale = new DatabaseSync(stalePath)
stale.exec(`PRAGMA user_version = ${SESSION_QUERY_SQLITE_SCHEMA_VERSION - 1}`)
stale.close()
const staleCtx = await liveContext({ path: stalePath })
staleCtx.sessions.create(SessionId('live'), { seed: messageEvents('needle') })
await staleCtx.sessionQuery.searchSessions({ query: 'needle' })
await (staleCtx.sessionQuery as SessionQuerySqlite).close()
await (staleCtx.sessionQuery as SqliteSessionQueryEngine).close()
const rebuilt = new DatabaseSync(stalePath)
expect((rebuilt.prepare('PRAGMA user_version').get() as { user_version: number }).user_version)
.toBe(SESSION_QUERY_SQLITE_SCHEMA_VERSION)
@@ -1259,7 +1259,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const augmentedPath = await temporaryPath('augmented.db')
const augmentedOwner = await liveContext({ path: augmentedPath })
await (augmentedOwner.sessionQuery as SessionQuerySqlite).close()
await (augmentedOwner.sessionQuery as SqliteSessionQueryEngine).close()
const augmented = new DatabaseSync(augmentedPath)
augmented.exec('CREATE TABLE unrelated(value TEXT)')
augmented.exec("INSERT INTO unrelated VALUES ('safe')")
@@ -1267,7 +1267,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
augmented.close()
const augmentedCtx = new Context()
await augmentedCtx.plugin(SessionStore)
await expect(augmentedCtx.plugin(SessionQuerySqlite, { path: augmentedPath }))
await expect(augmentedCtx.plugin(SqliteSessionQueryEngine, { path: augmentedPath }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
expect(augmentedCtx.sessionQuery).toBeUndefined()
const stillAugmented = new DatabaseSync(augmentedPath)
@@ -1277,14 +1277,14 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const currentAugmentedPath = await temporaryPath('current-augmented.db')
const currentAugmentedOwner = await liveContext({ path: currentAugmentedPath })
await (currentAugmentedOwner.sessionQuery as SessionQuerySqlite).close()
await (currentAugmentedOwner.sessionQuery as SqliteSessionQueryEngine).close()
const currentAugmented = new DatabaseSync(currentAugmentedPath)
currentAugmented.exec('CREATE TABLE unrelated(value TEXT)')
currentAugmented.exec("INSERT INTO unrelated VALUES ('safe')")
currentAugmented.close()
const currentAugmentedCtx = new Context()
await currentAugmentedCtx.plugin(SessionStore)
await expect(currentAugmentedCtx.plugin(SessionQuerySqlite, {
await expect(currentAugmentedCtx.plugin(SqliteSessionQueryEngine, {
path: currentAugmentedPath,
journalMode: 'delete',
})).rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
@@ -1304,7 +1304,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
foreign.close()
const foreignCtx = new Context()
await foreignCtx.plugin(SessionStore)
await expect(foreignCtx.plugin(SessionQuerySqlite, { path: foreignPath, journalMode: 'delete' }))
await expect(foreignCtx.plugin(SqliteSessionQueryEngine, { path: foreignPath, journalMode: 'delete' }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
expect(foreignCtx.sessionQuery).toBeUndefined()
const stillForeign = new DatabaseSync(foreignPath)
@@ -1320,7 +1320,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
wildcard.close()
const wildcardCtx = new Context()
await wildcardCtx.plugin(SessionStore)
await expect(wildcardCtx.plugin(SessionQuerySqlite, {
await expect(wildcardCtx.plugin(SqliteSessionQueryEngine, {
path: wildcardPath,
journalMode: 'delete',
})).rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
@@ -1338,7 +1338,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
otherApp.close()
const otherAppCtx = new Context()
await otherAppCtx.plugin(SessionStore)
await expect(otherAppCtx.plugin(SessionQuerySqlite, { path: otherAppPath }))
await expect(otherAppCtx.plugin(SqliteSessionQueryEngine, { path: otherAppPath }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
expect(otherAppCtx.sessionQuery).toBeUndefined()
})
@@ -1354,7 +1354,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
try {
const ctx = new Context()
await ctx.plugin(SessionStore)
await expect(ctx.plugin(SessionQuerySqlite, { path }))
await expect(ctx.plugin(SqliteSessionQueryEngine, { path }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
await new Promise<void>((resolve) => { setImmediate(resolve) })
expect(unhandled).toEqual([])
@@ -1372,18 +1372,18 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const lazyCtx = new Context()
await lazyCtx.plugin(SessionStore)
const lazy = await lazyCtx.plugin(SessionQuerySqlite, {
const lazy = await lazyCtx.plugin(SqliteSessionQueryEngine, {
path,
openAt: 'first-search',
})
expect(lazyCtx.sessionQuery).toBeInstanceOf(SessionQuerySqlite)
expect(lazyCtx.sessionQuery).toBeInstanceOf(SqliteSessionQueryEngine)
await expect(lazyCtx.sessionQuery.searchSessions({ query: 'needle' }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
await lazy.dispose()
const eagerCtx = new Context()
await eagerCtx.plugin(SessionStore)
await expect(eagerCtx.plugin(SessionQuerySqlite, { path }))
await expect(eagerCtx.plugin(SqliteSessionQueryEngine, { path }))
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
expect(eagerCtx.sessionQuery).toBeUndefined()
})
@@ -1681,7 +1681,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
}
const ctx = await liveContext()
await ctx.plugin(TestPersistence)
const search = ctx.sessionQuery as SessionQuerySqlite
const search = ctx.sessionQuery as SqliteSessionQueryEngine
const accepted = search.searchSessions({ query: 'needle' })
await started
const queued = search.searchSessions({ query: 'needle' })
@@ -1702,7 +1702,7 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
TestPersistence.reset()
const ctx = new Context()
await ctx.plugin(SessionStore)
const search = await ctx.plugin(SessionQuerySqlite, { path: ':memory:' })
const search = await ctx.plugin(SqliteSessionQueryEngine, { path: ':memory:' })
const persistence = await ctx.plugin(TestPersistence)
const optional = (ctx.sessionQuery as unknown as {
_optionalPersistenceFiber: Fiber
@@ -1725,8 +1725,8 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const searchPath = await temporaryPath('derived.db')
const ctx = new Context()
await ctx.plugin(SessionStore)
const persistence = await ctx.plugin(SessionPersistenceSqlite, { path: persistencePath })
const search = await ctx.plugin(SessionQuerySqlite, { path: searchPath })
const persistence = await ctx.plugin(SqliteSessionPersistence, { path: persistencePath })
const search = await ctx.plugin(SqliteSessionQueryEngine, { path: searchPath })
const meta = header('real', 10, { cwd: '/work' })
await ctx.sessionPersistence.create(meta)
await ctx.sessionPersistence.append(meta.id, messageEvents('real SQLite needle'))
@@ -1750,11 +1750,11 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const first = new Context()
await first.plugin(SessionStore)
const persistenceA = await first.plugin(SessionPersistenceSqlite, { path: persistencePathA })
const persistenceA = await first.plugin(SqliteSessionPersistence, { path: persistencePathA })
await first.sessionPersistence.create(shared)
await first.sessionPersistence.append(shared.id, messageEvents('alpha source'))
const inspectA = vi.spyOn(first.sessionPersistence, 'inspect')
const searchA = await first.plugin(SessionQuerySqlite, { path: searchPath })
const searchA = await first.plugin(SqliteSessionQueryEngine, { path: searchPath })
await expect(first.sessionQuery.searchSessions({ query: 'alpha' }))
.resolves.toMatchObject({ items: [{ header: shared }] })
expect(inspectA).toHaveBeenCalledTimes(1)
@@ -1763,9 +1763,9 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const reopened = new Context()
await reopened.plugin(SessionStore)
const persistenceAAgain = await reopened.plugin(SessionPersistenceSqlite, { path: persistencePathA })
const persistenceAAgain = await reopened.plugin(SqliteSessionPersistence, { path: persistencePathA })
const reopenedInspect = vi.spyOn(reopened.sessionPersistence, 'inspect')
const searchAAgain = await reopened.plugin(SessionQuerySqlite, { path: searchPath })
const searchAAgain = await reopened.plugin(SqliteSessionQueryEngine, { path: searchPath })
await expect(reopened.sessionQuery.searchSessions({ query: 'alpha' }))
.resolves.toMatchObject({ items: [{ header: shared }] })
expect(reopenedInspect).not.toHaveBeenCalled()
@@ -1774,11 +1774,11 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
const second = new Context()
await second.plugin(SessionStore)
const persistenceB = await second.plugin(SessionPersistenceSqlite, { path: persistencePathB })
const persistenceB = await second.plugin(SqliteSessionPersistence, { path: persistencePathB })
await second.sessionPersistence.create(shared)
await second.sessionPersistence.append(shared.id, messageEvents('bravo source'))
const inspectB = vi.spyOn(second.sessionPersistence, 'inspect')
const searchB = await second.plugin(SessionQuerySqlite, { path: searchPath })
const searchB = await second.plugin(SqliteSessionQueryEngine, { path: searchPath })
await expect(second.sessionQuery.searchSessions({ query: 'bravo' }))
.resolves.toMatchObject({ items: [{ header: shared }] })
await expect(second.sessionQuery.searchSessions({ query: 'alpha' })).resolves.toEqual({ items: [] })

View File

@@ -24,7 +24,7 @@
"path": "../../session/session-persistence"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
},
{
"path": "../session-query"

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session-query/session-query/README.md
README.md: 5d4d689477fca4fd6b01091f1304ff3065af7443
README.zh.md: e37de1b43fb67379ff70693d9eb7dc139db7e7a9
README.md: 02640bee4b3eb7f6877756a2ce84b26f698c9f58
README.zh.md: 794c0e6e86606d92ed82ec286844647487df3295

View File

@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
`SessionQueryService` is the combined abstract `ctx.sessionQuery` contract. It implements exact session-history retrieval, relationship tracing, and provider-independent filtering over live `ctx.sessions` plus optional dynamically mounted `ctx.sessionPersistence`; concrete backends implement its two full-text methods. Matching ids produce one record: live events win, while `live` and `persisted` report both source availabilities. Conflicting immutable headers fail with `SESSION_QUERY_SOURCE_CONFLICT`.
`SessionQueryEngine` is the combined abstract `ctx.sessionQuery` contract. It implements exact session-history retrieval, relationship tracing, and provider-independent filtering over live `ctx.sessions` plus optional dynamically mounted `ctx.sessionPersistence`; concrete backends implement its two full-text methods. Matching ids produce one record: live events win, while `live` and `persisted` report both source availabilities. Conflicting immutable headers fail with `SESSION_QUERY_SOURCE_CONFLICT`.
## Reads
@@ -27,7 +27,7 @@ The text clause is deliberately independent of FTS providers: caller text is esc
## Full-text methods
`SessionQueryService.searchSessions(request, exec?)` groups the logical corpus by strongest matching event; `searchEvents(request, exec?)` searches one logical session. These are the service's only abstract methods. Both return pages whose continuation is an owned branded `SessionSearchCursor`, accept optional cancellation, and expose snippets without provider-specific numeric scores. An event-search page also carries the cloned target header from the same indexed generation as its hits, allowing authorization consumers to bind policy to the payload observation. Search requests accept only metadata event filters, because literal-text filtering is the scan path described above.
`SessionQueryEngine.searchSessions(request, exec?)` groups the logical corpus by strongest matching event; `searchEvents(request, exec?)` searches one logical session. These are the service's only abstract methods. Both return pages whose continuation is an owned branded `SessionSearchCursor`, accept optional cancellation, and expose snippets without provider-specific numeric scores. An event-search page also carries the cloned target header from the same indexed generation as its hits, allowing authorization consumers to bind policy to the payload observation. Search requests accept only metadata event filters, because literal-text filtering is the scan path described above.
The package has no provider coordinator, fallback implementation, or standalone concrete plugin. A concrete service backend inherits the implemented reads, filters, and traces while owning full-text observation, reconciliation, ranking, cursor generations, and query execution; the first implementation is [`@deepseek-ai/dsh-session-query-sqlite`](../session-query-sqlite/README.md).

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
`SessionQueryService` 是组合式抽象 `ctx.sessionQuery` 约定。它对实时 `ctx.sessions` 和可选的动态挂载 `ctx.sessionPersistence` 实现精确会话历史取回、关系跟踪和与提供方无关的过滤;具体后端实现它的两个全文方法。匹配 id 只产生一条记录:实时事件优先,而 `live``persisted` 会报告两种来源的可用性。如果不可变 header 存在冲突,则以 `SESSION_QUERY_SOURCE_CONFLICT` 失败。
`SessionQueryEngine` 是组合式抽象 `ctx.sessionQuery` 约定。它对实时 `ctx.sessions` 和可选的动态挂载 `ctx.sessionPersistence` 实现精确会话历史取回、关系跟踪和与提供方无关的过滤;具体后端实现它的两个全文方法。匹配 id 只产生一条记录:实时事件优先,而 `live``persisted` 会报告两种来源的可用性。如果不可变 header 存在冲突,则以 `SESSION_QUERY_SOURCE_CONFLICT` 失败。
## 读取
@@ -27,7 +27,7 @@
## 全文方法
`SessionQueryService.searchSessions(request, exec?)` 按匹配最强的事件对逻辑语料库分组;`searchEvents(request, exec?)` 搜索一个逻辑会话。这两个是服务仅有的抽象方法。两者都返回分页结果,其延续信息是由服务持有的带品牌 `SessionSearchCursor`;接受可选取消,并在不使用提供方专用数值分数的情况下提供摘录。事件搜索分页结果还携带来自与命中相同索引世代的克隆目标 header使授权消费方可将策略绑定到此次载荷观察。搜索请求只接受事件元数据过滤器因为字面文本过滤使用上文所述扫描路径。
`SessionQueryEngine.searchSessions(request, exec?)` 按匹配最强的事件对逻辑语料库分组;`searchEvents(request, exec?)` 搜索一个逻辑会话。这两个是服务仅有的抽象方法。两者都返回分页结果,其延续信息是由服务持有的带品牌 `SessionSearchCursor`;接受可选取消,并在不使用提供方专用数值分数的情况下提供摘录。事件搜索分页结果还携带来自与命中相同索引世代的克隆目标 header使授权消费方可将策略绑定到此次载荷观察。搜索请求只接受事件元数据过滤器因为字面文本过滤使用上文所述扫描路径。
该包没有提供方协调器、回退实现或独立具体插件。具体服务后端继承已实现的读取、过滤和跟踪,同时负责全文观察、对账、排名、游标世代和查询执行;第一个实现是 [`@deepseek-ai/dsh-session-query-sqlite`](../session-query-sqlite/README.md)。

View File

@@ -67,7 +67,7 @@ export { assertSessionHeadersCompatible } from './sources.ts'
declare module '@deepseek-ai/cordis' {
interface Context {
sessionQuery: SessionQueryService
sessionQuery: SessionQueryEngine
}
}
@@ -78,7 +78,7 @@ declare module '@deepseek-ai/cordis' {
* A backend implements full-text observation, reconciliation, ranking, cursor
* generations, and query execution on the same `ctx.sessionQuery` service.
*/
export abstract class SessionQueryService extends Service {
export abstract class SessionQueryEngine extends Service {
static inject = ['sessions']
private readonly _readWindowMax: number
@@ -356,4 +356,4 @@ export abstract class SessionQueryService extends Service {
}
}
export default SessionQueryService
export default SessionQueryEngine

View File

@@ -17,7 +17,7 @@ import {
materializeSessionResultFilters,
type SessionQueryErrorCode,
} from '@deepseek-ai/dsh-session-query'
import { TestSessionQueryService } from './test-service.ts'
import { TestSessionQueryEngine } from './test-service.ts'
const id = SessionId('session')
@@ -274,7 +274,7 @@ describe('session-query document and filter helpers', () => {
it('exposes the scan path on the combined query service', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(TestSessionQueryService)
await ctx.plugin(TestSessionQueryEngine)
const session = ctx.sessions.create(id)
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'Alpha\n beta' }], source: { kind: 'user' },
@@ -290,7 +290,7 @@ describe('session-query document and filter helpers', () => {
it('registers exact and abstract search behavior under one ctx key', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const fiber = await ctx.plugin(TestSessionQueryService)
const fiber = await ctx.plugin(TestSessionQueryEngine)
const session = ctx.sessions.create(id)
await expect(ctx.sessionQuery.searchSessions({ query: 'AI' })).resolves.toEqual({ items: [] })
await expect(ctx.sessionQuery.searchEvents({ sessionId: id, query: 'AI' }))

View File

@@ -4,13 +4,13 @@ import { Context, type Fiber } from '@deepseek-ai/cordis'
import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionHeader, SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import SessionPersistence, { SessionPersistenceCorruptionError, SessionPersistenceRevision } from '@deepseek-ai/dsh-session-persistence'
import SessionQueryService, {
import SessionQueryEngine, {
SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
type SessionEventSurface,
type SessionQueryErrorCode,
} from '@deepseek-ai/dsh-session-query'
import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title'
import { TestSessionQueryService } from './test-service.ts'
import { TestSessionQueryEngine } from './test-service.ts'
function header(id: string, createdAt = 1, extra: Partial<SessionHeader> = {}): SessionHeader {
return { version: SESSION_FORMAT_VERSION, id: SessionId(id), createdAt, ...extra }
@@ -122,10 +122,10 @@ class TestPersistence extends SessionPersistence {
}
}
async function liveContext(config: ConstructorParameters<typeof TestSessionQueryService>[1] = {}): Promise<Context> {
async function liveContext(config: ConstructorParameters<typeof TestSessionQueryEngine>[1] = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(TestSessionQueryService, config)
await ctx.plugin(TestSessionQueryEngine, config)
return ctx
}
@@ -1176,7 +1176,7 @@ describe('session-query exact reads', () => {
const direct = new Context()
await direct.plugin(SessionStore)
expect(new TestSessionQueryService(direct)).toBeInstanceOf(SessionQueryService)
expect(new TestSessionQueryEngine(direct)).toBeInstanceOf(SessionQueryEngine)
for (const config of [
{ readWindowMax: -1 },
{ persistedInspectConcurrency: 0 },
@@ -1184,7 +1184,7 @@ describe('session-query exact reads', () => {
]) {
const invalid = new Context()
await invalid.plugin(SessionStore)
expect(() => new TestSessionQueryService(invalid, config))
expect(() => new TestSessionQueryEngine(invalid, config))
.toThrow(expectCode('SESSION_QUERY_INVALID_CONFIG'))
}
})
@@ -1192,8 +1192,8 @@ describe('session-query exact reads', () => {
it('leaves the optional persistence dependency optional', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const fiber = await ctx.plugin(TestSessionQueryService)
expect(ctx.sessionQuery).toBeInstanceOf(TestSessionQueryService)
const fiber = await ctx.plugin(TestSessionQueryEngine)
expect(ctx.sessionQuery).toBeInstanceOf(TestSessionQueryEngine)
await fiber.dispose()
expect(ctx.sessionQuery).toBeUndefined()
})
@@ -1202,7 +1202,7 @@ describe('session-query exact reads', () => {
TestPersistence.reset()
const ctx = new Context()
await ctx.plugin(SessionStore)
const query = await ctx.plugin(TestSessionQueryService)
const query = await ctx.plugin(TestSessionQueryEngine)
const persistence = await ctx.plugin(TestPersistence)
const optional = (ctx.sessionQuery as unknown as {
_corpus: { _optionalPersistenceFiber: Fiber }

View File

@@ -1,4 +1,4 @@
import SessionQueryService from '@deepseek-ai/dsh-session-query'
import SessionQueryEngine from '@deepseek-ai/dsh-session-query'
import type {
SessionEventSearchPage,
SessionEventSearchRequest,
@@ -9,7 +9,7 @@ import type {
} from '@deepseek-ai/dsh-session-query'
/** Test-only concrete query service for backend-independent behavior. */
export class TestSessionQueryService extends SessionQueryService {
export class TestSessionQueryEngine extends SessionQueryEngine {
override searchSessions(
_request: SessionSearchRequest,
_exec?: SessionSearchExecContext,

View File

@@ -5,7 +5,7 @@ import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/ds
import type { Session, SessionEvent, SessionHeader, SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import SessionPersistence from '@deepseek-ai/dsh-session-persistence'
import { type SessionQueryErrorCode } from '@deepseek-ai/dsh-session-query'
import { TestSessionQueryService } from './test-service.ts'
import { TestSessionQueryEngine } from './test-service.ts'
type MutableSessionHeader = { -readonly [K in keyof SessionHeader]: SessionHeader[K] }
@@ -99,7 +99,7 @@ class TracePersistence extends SessionPersistence {
async function queryContext(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(TestSessionQueryService)
await ctx.plugin(TestSessionQueryEngine)
return ctx
}

View File

@@ -30,7 +30,7 @@
"path": "../../session/session-persistence"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
}
]
}

View File

@@ -56,7 +56,7 @@
"@deepseek-ai/dsh-session-title": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"@deepseek-ai/dsh-timeout-policy": "workspace:^",
"@deepseek-ai/dsh-tool-call-timeout-policy": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}

View File

@@ -10,10 +10,10 @@ import SessionStore, {
SessionId,
type Session,
} from '@deepseek-ai/dsh-session'
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
import SessionQuerySqlite from '@deepseek-ai/dsh-session-query-sqlite'
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
import SqliteSessionQueryEngine from '@deepseek-ai/dsh-session-query-sqlite'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query'
const temporaryDirectories: string[] = []
@@ -38,9 +38,9 @@ describe('tool-session-query with the real SQLite provider', () => {
contexts.push(ctx)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
await ctx.plugin(SessionQuerySqlite, { path: join(root, 'session-query.db') })
await ctx.plugin(ToolRuntime)
await ctx.plugin(JsonlSessionPersistence, { root, compression: 'none' })
await ctx.plugin(SqliteSessionQueryEngine, { path: join(root, 'session-query.db') })
await ctx.plugin(ToolSessionQuery)
const persisted = SessionId('persisted')
@@ -107,9 +107,9 @@ describe('tool-session-query with the real SQLite provider', () => {
contexts.push(ctx)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
await ctx.plugin(SessionQuerySqlite, { path: join(root, 'session-query.db') })
await ctx.plugin(ToolRuntime)
await ctx.plugin(JsonlSessionPersistence, { root, compression: 'none' })
await ctx.plugin(SqliteSessionQueryEngine, { path: join(root, 'session-query.db') })
await ctx.plugin(ToolSessionQuery)
const base = Date.parse('2026-07-24T00:00:00.000Z')

View File

@@ -3,7 +3,7 @@ import { Context, type Fiber } from '@deepseek-ai/cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage, CallId, HarnessError , createMessage } from '@deepseek-ai/dsh-llm'
import { MAX_TIMER_DELAY_MS, TimeoutReason } from '@deepseek-ai/dsh-timeout'
import * as TimeoutPolicy from '@deepseek-ai/dsh-timeout-policy'
import * as TimeoutPolicy from '@deepseek-ai/dsh-tool-call-timeout-policy'
import SessionStore, {
SESSION_FORMAT_VERSION,
SessionId,
@@ -11,7 +11,7 @@ import SessionStore, {
type SessionHeader,
type SessionId as SessionIdValue,
} from '@deepseek-ai/dsh-session'
import SessionQueryService, {
import SessionQueryEngine, {
SessionQueryError,
SessionSearchCursor,
type SessionEventSearchHit,
@@ -25,7 +25,7 @@ import SessionQueryService, {
type SessionTitleObservationResult,
} from '@deepseek-ai/dsh-session-query'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { type ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import ToolRuntime, { type ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query'
const activeContexts: Context[] = []
@@ -111,7 +111,7 @@ function eventHit(sessionId: SessionIdValue, seq: number, text = 'needle excerpt
}
}
class FakeQuery extends SessionQueryService {
class FakeQuery extends SessionQueryEngine {
static sessionSearch: (
request: SessionSearchRequest,
exec?: SessionSearchExecContext,
@@ -204,7 +204,7 @@ async function mount(
activeContexts.push(ctx)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
if (enforceTimeout) await ctx.plugin(TimeoutPolicy)
await ctx.plugin(FakeQuery)
const fiber = await ctx.plugin(ToolSessionQuery, config)

View File

@@ -31,7 +31,7 @@
"path": "../session-query"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
},
{
"path": "../../util/timeout"