fix(web): hide verified cold blank sessions

This commit is contained in:
_Kerman
2026-08-13 14:04:16 +08:00
parent 2c456a00ea
commit 3c06c66ac4
26 changed files with 548 additions and 125 deletions
@@ -7,7 +7,7 @@
* pushed to mux consumers as a session/projection frame minted here.
*/
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { z } from 'zod'
import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent'
@@ -163,10 +163,29 @@ describe('session.history projections block', () => {
dispose()
const after = await proxy.sessions.history(request({ sessionId: session.id }))
if (!after.result.ok) throw new Error('unreachable')
// The registry is still mounted, so the block itself stays (asOfSeq cut
// with zero keys); the disposed key reads as capability absence.
// The registry stays mounted; only the disposed key leaves while the
// gateway-owned Session-list unit remains.
expect(after.result.value.projections?.asOfSeq).toBe(session.seq - 1)
expect(after.result.value.projections?.values).toEqual({})
expect('test/last-user' in (after.result.value.projections?.values ?? {})).toBe(false)
expect(after.result.value.projections?.values.sessionListMetadata).toEqual({
blank: true,
lastPromptAt: session.events.at(-1)?.time,
})
})
it('removes the gateway-owned Session-list unit when the gateway fiber unloads', async () => {
const { ctx, session } = await harness(true)
expect('sessionListMetadata' in ctx.sessionProjections.snapshot(session).values).toBe(false)
const fiber = ctx.plugin(Object.assign((gatewayCtx: Context) => {
createApiProxy(gatewayCtx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
}, { inject: ['sessions', 'agents', 'userQuestions', 'sessionProjections'] }))
await fiber.await()
await vi.waitFor(() => {
expect(ctx.sessionProjections.snapshot(session).values.sessionListMetadata)
.toEqual({ blank: true, lastPromptAt: null })
})
await fiber.dispose()
expect('sessionListMetadata' in ctx.sessionProjections.snapshot(session).values).toBe(false)
})
})
@@ -174,11 +193,18 @@ describe('session.list projections column', () => {
it('serves attached rows from the live registry cut, watermarked for client seeding', async () => {
const { ctx, session } = await harness(true)
ctx.sessionProjections.register(lastUserUnit())
const gateway = api(ctx)
await new Promise(resolve => setTimeout(resolve, 0))
session.append('turn/start', { turn: 1 })
seedMessages(session, 1)
const response = await api(ctx).sessions.list(request({}))
const response = await gateway.sessions.list(request({}))
if (!response.result.ok) throw new Error('unreachable')
const row = response.result.value.items.find(item => item.sessionId === session.id)
expect(row?.projections?.values['test/last-user']).toEqual({ text: 'm0' })
expect(row?.projections?.values.sessionListMetadata).toEqual({
blank: false,
lastPromptAt: session.events.at(-1)?.time,
})
expect(row?.projections?.asOfSeq).toBe(session.seq - 1)
})
@@ -266,21 +292,33 @@ describe('session/projection push frame', () => {
await new Promise(resolve => setTimeout(resolve, 0))
const abort = new AbortController()
const stream = proxy.events.mux({ rpcId: RpcId('t-proj-mux'), payload: {} }, abort.signal)
const collected = collect(stream, 2, abort)
const collected = collect(stream, 5, abort)
const now = vi.spyOn(Date, 'now').mockReturnValue(100)
seedMessages(session, 1)
// Same-reference apply: turn/start does not concern the unit — no frame.
now.mockReturnValue(200)
session.append('turn/start', { turn: 1 })
now.mockReturnValue(300)
seedMessages(session, 1)
now.mockRestore()
const frames = await collected
const pushes = frames.filter(
(f): f is Extract<MuxFrame, { type: 'session/projection' }> => f.type === 'session/projection',
(f): f is Extract<MuxFrame, { type: 'session/projection' }> =>
f.type === 'session/projection' && f.key === 'test/last-user',
)
expect(pushes).toEqual([
{ type: 'session/projection', sessionId: session.id, key: 'test/last-user', value: { text: 'm0' }, seq: 0 },
{ type: 'session/projection', sessionId: session.id, key: 'test/last-user', value: { text: 'm0' }, seq: 2 },
])
expect(frames.filter(
(f): f is Extract<MuxFrame, { type: 'session/projection' }> =>
f.type === 'session/projection' && f.key === 'sessionListMetadata',
)).toEqual([
{ type: 'session/projection', sessionId: session.id, key: 'sessionListMetadata', value: { blank: true, lastPromptAt: 100 }, seq: 0 },
{ type: 'session/projection', sessionId: session.id, key: 'sessionListMetadata', value: { blank: false, lastPromptAt: 100 }, seq: 1 },
{ type: 'session/projection', sessionId: session.id, key: 'sessionListMetadata', value: { blank: false, lastPromptAt: 300 }, seq: 2 },
])
// Frame seq aligns with the tail block's asOfSeq vocabulary (higher-seq-wins compatible).
const tail = await proxy.sessions.history(request({ sessionId: session.id }))
if (!tail.result.ok) throw new Error('unreachable')