Merge remote-tracking branch 'origin/master' into codex/basic-session-search
Conflict resolutions: - `session.list`: master's projection columns fold into the PR's cancellable, batched `listVisibleSessionSummaries`, which `session.search` shares as its visibility baseline; master's goal helpers stay beside it. - Client sessions face: master narrowed `ctx.sessions` to `ISessions`, so the search verb and its protocol-constant bound are declared there and the test-runtime double implements them (recorded, empty page unless a scenario stubs hits). - `WorkspaceBrowser`: master's per-row Rename wiring rides the PR's search results view; the tree keeps the PR's query-free derivations. - `dsh web` bin: the PR's shutdown-handlers-before-readiness order with master's boot-time LAN address snapshot. - `session-query-sqlite`: master's `SCHEMA_VERSION` 7 stands; the PR's bump carried no schema change. - Specs: master wraps assistant/steering message payloads and requires an `application/json` carrier request, so the search fixtures and tests follow. - Web aria goldens keep master's recording plus the PR's search placeholder; the navigation-panes inventory keeps master's terminal-card golden next to the PR's search-results golden.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
/**
|
||||
* Keyless real-Loader-path smoke for the combined SQLite session-query service.
|
||||
*
|
||||
@@ -48,7 +49,9 @@ describe('dsh-session-query-sqlite real Loader path', () => {
|
||||
type: 'user/message',
|
||||
seq: 0,
|
||||
time: 10,
|
||||
data: { content: [{ type: 'text', text: 'real Loader needle' }], source: { kind: 'user' } },
|
||||
data: createUserMessage({
|
||||
content: [{ type: 'text', text: 'real Loader needle' }], source: { kind: 'user' },
|
||||
}),
|
||||
surfaceOp: 'append',
|
||||
}])
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createAssistantMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context, type Fiber } from 'cordis'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
@@ -44,7 +45,9 @@ function messageEvents(text: string, time = 1): SessionEvent[] {
|
||||
type: 'user/message',
|
||||
seq: 0,
|
||||
time,
|
||||
data: { content: [{ type: 'text', text }], source: { kind: 'user' } },
|
||||
data: createUserMessage({
|
||||
content: [{ type: 'text', text }], source: { kind: 'user' },
|
||||
}),
|
||||
surfaceOp: 'append',
|
||||
}]
|
||||
}
|
||||
@@ -146,6 +149,11 @@ class TestPersistence extends SessionPersistence {
|
||||
return structuredClone(entry)
|
||||
}
|
||||
|
||||
async readFrom(id: SessionIdType, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> {
|
||||
const whole = await this.inspect(id, signal)
|
||||
return { meta: whole.meta, events: whole.events.filter(event => event.seq >= fromSeq) }
|
||||
}
|
||||
|
||||
async list(): Promise<SessionHeader[]> {
|
||||
TestPersistence.listStarted?.()
|
||||
await TestPersistence.listGate
|
||||
@@ -276,7 +284,9 @@ describe('SQLite session search', () => {
|
||||
})
|
||||
session.append(
|
||||
'user/message',
|
||||
{ content: [{ type: 'text', text: 'An AI helper' }], source: { kind: 'user' } },
|
||||
createUserMessage({
|
||||
content: [{ type: 'text', text: 'An AI helper' }], source: { kind: 'user' },
|
||||
}),
|
||||
{ surfaceOp: 'append' },
|
||||
)
|
||||
|
||||
@@ -297,11 +307,13 @@ describe('SQLite session search', () => {
|
||||
{
|
||||
turn: 1,
|
||||
step: 1,
|
||||
content: [
|
||||
{ type: 'reasoning', text: 'private-chain-marker' },
|
||||
{ type: 'text', text: 'visible-answer-marker' },
|
||||
],
|
||||
provenance: { provider: 'mock', model: 'mock' },
|
||||
message: createAssistantMessage({
|
||||
content: [
|
||||
{ type: 'reasoning', text: 'private-chain-marker' },
|
||||
{ type: 'text', text: 'visible-answer-marker' },
|
||||
],
|
||||
source: { provider: 'mock', model: 'mock' },
|
||||
}),
|
||||
},
|
||||
{ surfaceOp: 'append' },
|
||||
)
|
||||
@@ -321,9 +333,13 @@ describe('SQLite session search', () => {
|
||||
const ctx = await liveContext({ path: ':memory:', defaultLimit: 10, maxLimit: 20 })
|
||||
const parent = SessionId('parent')
|
||||
const events: SessionEvent[] = [
|
||||
{ type: 'user/message', seq: 0, time: 10, data: { content: [{ type: 'text', text: 'needle original' }], source: { kind: 'user' } }, surfaceOp: 'append' },
|
||||
{ type: 'user/message', seq: 0, time: 10, data: createUserMessage({
|
||||
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 }, sourceEventSeqs: [0] },
|
||||
{ type: 'user/message', seq: 2, time: 12, data: createUserMessage({
|
||||
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 } })
|
||||
@@ -542,7 +558,9 @@ describe('SQLite session search', () => {
|
||||
cursor: eventPage.nextCursor,
|
||||
})).rejects.toThrow(expectCode('SESSION_QUERY_INVALID_CURSOR'))
|
||||
|
||||
target.append('user/message', { content: [{ type: 'text', text: 'needle four' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
|
||||
target.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'needle four' }], source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
await expect(ctx.sessionQuery.searchEvents({
|
||||
sessionId: target.id,
|
||||
query: 'needle',
|
||||
@@ -718,7 +736,9 @@ describe('SQLite reconciliation and source lifecycle', () => {
|
||||
await expect(ctx.sessionQuery.searchSessions({ query: 'durable' }))
|
||||
.resolves.toMatchObject({ items: [{ header: durable, live: false, persisted: true }] })
|
||||
const live = ctx.sessions.prepare(shared.id, { meta: { createdAt: 10, cwd: '/work' } })
|
||||
live.append('user/message', { content: [{ type: 'text', text: 'live needle' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
|
||||
live.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'live needle' }], source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
const detach = ctx.sessions.enter(live)
|
||||
ctx.sessions.announce(live)
|
||||
|
||||
@@ -1151,7 +1171,9 @@ describe('SQLite reconciliation and source lifecycle', () => {
|
||||
await ctx.sessionQuery.searchEvents({ sessionId: live.id, query: 'base' })
|
||||
const db = (ctx.sessionQuery as unknown as { _db: DatabaseSync })._db
|
||||
db.exec('PRAGMA query_only = ON')
|
||||
live.append('user/message', { content: [{ type: 'text', text: 'retry needle' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
|
||||
live.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'retry needle' }], source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
await expect(ctx.sessionQuery.searchEvents({ sessionId: live.id, query: 'needle' }))
|
||||
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
|
||||
db.exec('PRAGMA query_only = OFF')
|
||||
|
||||
Reference in New Issue
Block a user