Files
deepseek-harness/apps/web/tests/assembled-remote.ts
T
Tianyi Cui 1613a10c03 merge: integrate pinned master into session-log-v4
Merge master 7e2b049d8e2523127a3b4fba38cab4748ef0c72f, observed on
2026-09-19 at 07:38:37 UTC, into release/session-log-v4 at
c4663e8671e33bc2cfdbc868b0b18d979508d39b. Keep this base integration
separate from the five persistence repair PRs so their review diffs
remain focused and can be rebased without moving their fixes here.

Combine master's pin ordering and archived-session filter with V4's
parent-owned direct child catalog. Both grouped and flat sidebar rows
must pass the Session list to the V4 activity calculation; restoring
master's summary-lineage traversal would reintroduce the removed
subagent ownership and count unrelated descendants.

Keep master's removal of the archived-session settings page. Its only
release-side test change renamed a Session list fixture field, so no
runtime behavior is lost by deleting that obsolete test. Adapt the
release-side direct-child-status regression to the new row/filter
arguments and preserve its assertions.

Retain master's workspace ordering and row-motion documentation while
keeping the V4 child-catalog wording. Reconcile the generated bilingual
records against the merged documents. Frozen V0-V3 codecs, recorded
Session JSONL inputs, and the current V4 persistence schema are unchanged.

Refs #2692, #3273
2026-09-19 15:42:11 +08:00

424 lines
15 KiB
TypeScript

/**
* RemoteMock scenario for built-client tests that do not own a Host.
* The adjacent JSON is maintained with this module when Remote responses or
* the current Session header version change.
* Fixture Sessions have no retained Host terminals to restore.
*/
import { readFileSync } from 'node:fs'
import { randomUUID } from 'node:crypto'
import { join } from 'node:path'
import { ok, RemoteMock } from '@deepseek-ai/dsh-remote-mock'
import { remoteDefaultResponses } from '@deepseek-ai/dsh-client-test-runtime/src/assembly/remote-default-responses.ts'
interface SessionSummary {
readonly sessionId: string
updatedAt: number
running: boolean
blank: boolean
readonly parentSessionId?: string
readonly origin?: 'subagent'
readonly cwd?: string
readonly projections?: {
readonly asOfSeq: number
readonly values: Readonly<Record<string, unknown>>
}
}
interface WorkspaceView {
readonly workspaceId: string
readonly path: string
readonly title: string
sessionIds: string[]
readonly createdAt: string
updatedAt: string
}
interface EventRecord {
readonly type: 'event'
readonly event: {
readonly seq: number
readonly time: number
readonly type: string
readonly data: unknown
readonly surfaceOp?: string
}
}
interface FollowSnapshot {
readonly type: 'snapshot'
readonly header: Readonly<Record<string, unknown>>
readonly cursor: number
readonly records: readonly EventRecord[]
readonly hasMore: boolean
readonly projections: Readonly<Record<string, unknown>>
readonly assistantStream?: Readonly<Record<string, unknown>>
}
interface ControlBaseline {
readonly type: 'baseline'
readonly value: {
readonly queues: Readonly<Record<string, readonly unknown[]>>
readonly jobs: Readonly<Record<string, readonly unknown[]>>
readonly approvals: readonly unknown[]
readonly questions: readonly unknown[]
readonly projections: Readonly<Record<string, {
readonly asOfSeq: number
readonly values: Readonly<Record<string, unknown>>
}>>
}
}
interface CapturedFixture {
readonly sessionList: { readonly ok: true; readonly value: { readonly items: readonly SessionSummary[] } }
readonly settingsDescribe: {
readonly ok: true
readonly value: {
readonly writable: boolean
readonly hasDocument: boolean
readonly namespaces: readonly unknown[]
}
}
readonly credentialsDescribe: unknown
readonly modelCatalog: unknown
readonly agentPresets: unknown
readonly commands: unknown
readonly workspace: {
readonly type: 'baseline'
readonly value: {
readonly items: readonly WorkspaceView[]
readonly archivedSessionIds: readonly string[]
readonly pinnedSessionIds: readonly string[]
}
}
readonly control: ControlBaseline
readonly remoteEvents: readonly unknown[]
readonly follow: FollowSnapshot
readonly attachment: unknown
}
export interface AssembledRemoteOptions {
/** Supply an enabled Host preference for diagnostic View scenarios. */
readonly developerTools?: boolean
/** Return the fixture's image-dimension admission error from Session prompt. */
readonly rejectPrompt?: boolean
}
export interface AssembledRemote {
readonly mock: RemoteMock
}
const fixtureSource = readFileSync(
join(process.cwd(), 'apps/web/tests/fixtures/assembled-remote.fixture.json'),
'utf8',
)
/** Create one isolated RemoteMock world for an assembled built-client case. */
export function createAssembledRemote(options: AssembledRemoteOptions = {}): AssembledRemote {
const fixture = JSON.parse(fixtureSource) as CapturedFixture
const sessions = [...structuredClone(fixture.sessionList.value.items)]
const workspaces = [...structuredClone(fixture.workspace.value.items)]
const records = new Map<string, EventRecord[]>([[
'fx-alpha',
structuredClone(fixture.follow.records) as EventRecord[],
]])
const nextTurns = new Map([...records].map(([sessionId, sessionRecords]) => {
let next = 0
for (const { event } of sessionRecords) {
if (event.type !== 'turn/start' || !isRecord(event.data)) continue
const turn = event.data['turn']
if (typeof turn === 'number') next = Math.max(next, turn + 1)
}
return [sessionId, next] as const
}))
const attachments = new Map<string, unknown>([['fixture:image', structuredClone(fixture.attachment)]])
const blankSessionProjections = fixture.control.value.projections['fx-gamma']
if (blankSessionProjections === undefined) {
throw new Error('assembled fixture: blank Session projections missing')
}
let nextSession = 1
const mock = RemoteMock.create().load(remoteDefaultResponses)
mock.load({
unary: {
'settings/describe': options.developerTools === true
? ok({
...fixture.settingsDescribe.value,
namespaces: [...fixture.settingsDescribe.value.namespaces, {
ns: 'ui-developer-tools',
schema: { type: 'object', dict: { enabled: { type: 'boolean' } } },
value: { enabled: true }, applies: 'live', secrets: [], revision: 0,
}],
})
: structuredClone(fixture.settingsDescribe),
'credentials/describe': structuredClone(fixture.credentialsDescribe),
'session/modelCatalog': structuredClone(fixture.modelCatalog),
'agentPresets/list': structuredClone(fixture.agentPresets),
'commands/list': structuredClone(fixture.commands),
'settings/canOpenAgentPresetDirectory': ok(true),
'settings/openSettingsDocument': ok({ opened: true }),
'settings/openAgentPresetDirectory': ok({ opened: true }),
'terminal/list': ok([]),
'skills/list': ok({ skills: [] }),
'session/canOpenWorkspacePath': ok(true),
'session/openWorkspacePath': ok({ opened: true }),
'session/updateQueue': {
ok: false,
error: {
code: 'session/queue-item-not-found',
message: 'assembled fixture has no pending queue item',
details: {},
},
},
'session/cancel': ok({ accepted: true }),
},
})
mock.stream('$events', (_args, stream) => {
for (const frame of fixture.remoteEvents) stream.push(structuredClone(frame))
})
mock.stream('session/control', (_args, stream) => {
stream.push(structuredClone(fixture.control))
})
mock.stream('workspace/follow', (_args, stream) => {
stream.push({
type: 'baseline',
value: {
items: structuredClone(workspaces),
archivedSessionIds: structuredClone(fixture.workspace.value.archivedSessionIds),
pinnedSessionIds: structuredClone(fixture.workspace.value.pinnedSessionIds),
},
})
})
mock.stream('session/follow', ([args], stream) => {
const request = recordValue(args, 'request')
const sessionId = followedSessionId(request)
if (sessionId === 'fx-alpha') {
stream.push(structuredClone(fixture.follow))
return
}
const summary = sessions.find(candidate => candidate.sessionId === sessionId)
if (summary === undefined) throw new Error(`assembled fixture: no Session ${sessionId}`)
const sessionRecords = records.get(sessionId) ?? []
stream.push({
type: 'snapshot',
header: {
version: 3,
id: sessionId,
createdAt: summary.updatedAt,
cwd: summary.cwd,
isSeeded: false,
},
cursor: sessionRecords.at(-1)?.event.seq ?? -1,
records: structuredClone(sessionRecords),
hasMore: false,
projections: structuredClone(blankSessionProjections),
...isAssistantStreamRequested(request) ? { assistantStream: { revision: 0 } } : {},
})
})
mock.unary('$events/result', (result: unknown) => {
const eventId = recordString(result, 'eventId')
mock.streams.push('$events', { type: 'cancel', eventId })
return ok(undefined)
})
mock.unary('session/list', () => ok({ items: structuredClone(sessions) }))
mock.unary('session/projections', (request: unknown) => {
const sessionId = recordString(recordValue(request, 'request'), 'sessionId')
const summary = sessions.find(candidate => candidate.sessionId === sessionId)
return ok(structuredClone(summary?.projections ?? fixture.control.value.projections[sessionId] ?? null))
})
mock.unary('workspace/create', (request: unknown) => {
const path = recordString(recordValue(request, 'request'), 'path')
const existing = workspaces.find(workspace => workspace.path === path)
if (existing !== undefined) return ok({ workspace: structuredClone(existing), created: false })
const now = new Date().toISOString()
const workspace = {
workspaceId: `fx-ws-${String(workspaces.length + 1)}`,
path,
title: path.split('/').filter(Boolean).at(-1) ?? path,
sessionIds: [],
createdAt: now,
updatedAt: now,
}
workspaces.unshift(workspace)
mock.streams.push('workspace/follow', { type: 'upsert', workspace: structuredClone(workspace) })
return ok({ workspace: structuredClone(workspace), created: true })
})
mock.unary('session/create', (request: unknown) => {
request = recordValue(request, 'request')
const requestedId = optionalRecordString(request, 'sessionId')
const sessionId = requestedId ?? `fx-${String(nextSession++)}`
const existing = sessions.find(candidate => candidate.sessionId === sessionId)
if (existing !== undefined) return ok({ sessionId })
const workspaceId = optionalRecordString(request, 'workspaceId')
const workspace = workspaces.find(candidate => candidate.workspaceId === workspaceId)
const cwd = workspace?.path ?? optionalRecordString(request, 'cwd') ?? '/tmp/fixture'
const summary: SessionSummary = {
sessionId,
updatedAt: Date.now(),
running: false,
blank: true,
cwd,
projections: structuredClone(blankSessionProjections),
}
sessions.push(summary)
records.set(sessionId, [])
nextTurns.set(sessionId, 0)
if (workspace !== undefined && !workspace.sessionIds.includes(sessionId)) {
workspace.sessionIds = [sessionId, ...workspace.sessionIds]
workspace.updatedAt = new Date().toISOString()
mock.streams.push('workspace/follow', {
type: 'upsert',
workspace: structuredClone(workspace),
})
}
mock.streams.push('$events', { type: 'emit', event: 'api-session/added', args: [structuredClone(summary)] })
return ok({ sessionId })
})
mock.unary('session/attachment', (request: unknown) => {
request = recordValue(request, 'request')
const attachmentId = recordString(request, 'attachmentId')
return attachments.get(attachmentId) ?? {
ok: false,
error: {
code: 'session/attachment-invalid',
message: `assembled fixture attachment ${attachmentId} is missing`,
details: { reason: 'ATTACHMENT_NOT_FOUND' },
},
}
})
mock.unary('session/prompt', (request: unknown) => {
request = recordValue(request, 'request')
if (options.rejectPrompt === true) {
return {
ok: false,
error: {
code: 'session/attachment-invalid',
message: 'assembled fixture: image side exceeds the deployment limit',
details: { reason: 'IMAGE_DIMENSION_TOO_LARGE' },
},
}
}
const sessionId = recordString(request, 'sessionId')
const requestId = recordString(request, 'requestId')
const sessionRecords = records.get(sessionId) ?? []
const summary = sessions.find(candidate => candidate.sessionId === sessionId)
if (summary === undefined) throw new Error(`assembled fixture: no Session ${sessionId}`)
const content = recordArray(request, 'content').map((part) => {
if (!isRecord(part) || part.type !== 'image') return part
const attachmentId = `assembled:${randomUUID()}`
const data = recordString(part, 'data')
const attachment = {
attachmentId,
mediaType: recordString(part, 'mediaType'),
bytes: Math.max(1, Math.floor(data.length * 3 / 4)),
width: 160,
height: 90,
...optionalRecordString(part, 'name') === undefined
? {}
: { name: optionalRecordString(part, 'name') },
}
attachments.set(attachmentId, ok({ attachment, data }))
return { type: 'image', attachment }
})
const turn = nextTurns.get(sessionId) ?? 0
nextTurns.set(sessionId, turn + 1)
summary.updatedAt = Date.now()
summary.blank = false
if (!summary.running) {
summary.running = true
mock.streams.push('$events', { type: 'emit', event: 'api-session/status', args: [sessionId, true] })
}
const turnEvent = eventOf(sessionRecords.length, 'turn/start', { turn })
const userEvent = eventOf(sessionRecords.length + 1, 'user/message', {
content,
source: { kind: 'user', rpcId: requestId },
role: 'user',
id: randomUUID(),
}, 'append')
sessionRecords.push(turnEvent, userEvent)
records.set(sessionId, sessionRecords)
mock.streams.push('session/follow', turnEvent, follows(sessionId))
mock.streams.push('session/follow', userEvent, follows(sessionId))
return ok({ accepted: true })
})
mock.unary('commands/execute', (args: unknown) => {
const line = optionalRecordString(args, 'line') ?? ''
const name = /^\/(\S+)/u.exec(line.trim())?.[1]
if (name === undefined || !['compact', 'echo', 'goal', 'permission', 'plan'].includes(name)) {
return ok(undefined)
}
return ok({
commandId: `assembled-command-${randomUUID()}`,
result: {
kind: 'success',
...name === 'echo' ? { text: line.replace(/^\/echo\s*/u, '') } : {},
},
})
})
return { mock }
}
function eventOf(
seq: number,
type: string,
data: unknown,
surfaceOp?: string,
): EventRecord {
return {
type: 'event',
event: {
seq,
time: Date.now(),
type,
data,
...(surfaceOp === undefined ? {} : { surfaceOp }),
},
}
}
function follows(sessionId: string): (args: readonly unknown[]) => boolean {
return ([args]) => followedSessionId(recordValue(args, 'request')) === sessionId
}
function followedSessionId(value: unknown): string {
if (!isRecord(value) || !isRecord(value.address)) throw new TypeError('assembled fixture follow request is invalid')
return value.address.kind === 'session'
? recordString(value.address, 'sessionId')
: recordString(value.address, 'childSessionId')
}
function isAssistantStreamRequested(value: unknown): boolean {
return isRecord(value) && value.assistantStream === true
}
function recordString(value: unknown, key: string): string {
const selected = isRecord(value) ? value[key] : undefined
if (typeof selected !== 'string') throw new TypeError(`assembled fixture ${key} must be a string`)
return selected
}
function optionalRecordString(value: unknown, key: string): string | undefined {
const selected = isRecord(value) ? value[key] : undefined
if (selected === undefined) return undefined
if (typeof selected !== 'string') throw new TypeError(`assembled fixture ${key} must be a string`)
return selected
}
function recordArray(value: unknown, key: string): readonly unknown[] {
const selected = isRecord(value) ? value[key] : undefined
if (!Array.isArray(selected)) throw new TypeError(`assembled fixture ${key} must be an array`)
return selected
}
function recordValue(value: unknown, key: string): unknown {
if (!isRecord(value) || !(key in value)) throw new TypeError(`assembled fixture ${key} is missing`)
return value[key]
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}