@@ -5,8 +5,27 @@
// prompt triggers a chunked streaming replay; cancel stops the replay; resident pending
// approval/question requests exercise replay and composer takeover with stable rpcIds.
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
import type { SessionEvent , SessionId , TodoItem } from '@deepseek-ai/dsh-session/types'
import {
createAssistantMessage ,
createToolResultMessage ,
createUserMessage ,
} from '@deepseek-ai/dsh-llm/message'
import { CallId } from '@deepseek-ai/dsh-llm/brand'
import type {
AssistantMessage ,
ContentBlock ,
MessageSource ,
ToolResultMessage ,
UserMessage ,
} from '@deepseek-ai/dsh-llm'
import type {
SessionEvent ,
SessionId ,
TodoItem ,
} from '@deepseek-ai/dsh-session/types'
// Type-only: the brand constructor is host-side; the fixture casts at its
// wire-fabrication boundary (the schema layer's one-cast-point posture).
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
import type {
ApiProxy , ClientRequest , ClientResponse , HistoryEntry , HostFrame , MuxFrame , RpcReceipt ,
ModelTarget , RpcRequest , RpcResponse , RpcResult , ServerRequest , ServerResponse , SessionSummary ,
@@ -24,6 +43,21 @@ function text(t: string): ContentBlock[] {
return [ { type : 'text' , text : t } ]
}
function userMessage ( content : ContentBlock [ ] , source : MessageSource = { kind : 'user' } ) : UserMessage {
return createUserMessage ( { content , source } )
}
function assistantMessage ( content : ContentBlock [ ] ) : AssistantMessage {
return createAssistantMessage ( {
content ,
source : { provider : 'fixture' , model : 'fx-1' } ,
} )
}
function toolResultMessage ( callId : string , content : ContentBlock [ ] , isError : boolean ) : ToolResultMessage {
return createToolResultMessage ( { callId : CallId ( callId ) , content , isError } )
}
const MARKDOWN_FIXTURE = [
'# Markdown fixture' ,
'' ,
@@ -83,10 +117,7 @@ function buildAlphaLog(): SessionEvent[] {
push ( { type : 'turn/start' , data : { turn , trigger : { kind : 'message' , source : { kind : 'user' } } } } )
const userSeq = push ( {
type : 'user/message' , surfaceOp : 'append' ,
data : {
content : text ( turn === 59 ? USER_MARKDOWN_LITERAL : ` 问题 ${ turn } : fixture 历史消息,用于翻页与渲染验收。` ) ,
source : { kind : 'user' } ,
} ,
data : userMessage ( text ( turn === 59 ? USER_MARKDOWN_LITERAL : ` 问题 ${ turn } : fixture 历史消息,用于翻页与渲染验收。` ) ) ,
} )
if ( turn === 0 ) {
push ( {
@@ -95,7 +126,7 @@ function buildAlphaLog(): SessionEvent[] {
} )
}
if ( turn % 9 === 4 ) {
push ( { type : 'user/message' , surfaceOp : 'append' , data : { content : text( ` [fixture] 上下文注入( turn ${ turn } ) ` ) , source : { kind : 'plugin' , plugin : 'fixture' } } } )
push ( { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( text ( ` [fixture] 上下文注入( turn ${ turn } ) ` ) , { kind : 'plugin' , plugin : 'fixture' } ) } )
}
push ( { type : 'step/start' , data : { turn , step : 0 } } )
const withTool = turn % 5 === 2
@@ -106,19 +137,19 @@ function buildAlphaLog(): SessionEvent[] {
if ( withTool ) {
const callId = ` fx-call- ${ turn } `
blocks . push ( { type : 'tool-call' , id : callId , name : 'echo' , arguments : ` {"text":"turn ${ turn } "} ` } as ContentBlock )
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 0 , content : blocks , provenance : { provider : 'fixture' , model : 'fx-1' } } } )
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 0 , message : assistantMessage ( blocks ) } } )
push ( { type : 'tool/call' , data : { turn , step : 0 , callId , name : 'echo' , arguments : ` {"text":"turn ${ turn } "} ` } } )
push ( { type : 'tool/result' , surfaceOp : 'append' , data : { turn , step : 0 , callId , content : text( ` ECHO: TURN ${ turn } ` ) , isError : turn % 25 === 12 } } )
push ( { type : 'tool/result' , surfaceOp : 'append' , data : { turn , step : 0 , message : toolResultMessage ( callId, text ( ` ECHO: TURN ${ turn } ` ) , turn % 25 === 12 ) } } )
push ( { type : 'step/end' , data : { turn , step : 0 } } )
push ( { type : 'step/start' , data : { turn , step : 1 } } )
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 1 , content : text( ` 工具结果已消化( turn ${ turn } )。 ` ) , provenance : { provider : 'fixture' , model : 'fx-1' } } } )
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 1 , message : assistantMessage ( text( ` 工具结果已消化( turn ${ turn } )。 ` ) ) } } )
push ( { type : 'step/end' , data : { turn , step : 1 } } )
} else {
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 0 , content : blocks , provenance : { provider : 'fixture' , model : 'fx-1' } } } )
push ( { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step : 0 , message : assistantMessage ( blocks ) } } )
push ( { type : 'step/end' , data : { turn , step : 0 } } )
}
if ( turn % 13 === 6 ) {
push ( { type : 'steering/message' , surfaceOp : 'append' , data : { turn , content : text( ` 插话 ${ turn } : fixture steering 消息。` ) , source : { kind : 'user' } } } )
push ( { type : 'steering/message' , surfaceOp : 'append' , data : { turn , message : userMessage ( text( ` 插话 ${ turn } : fixture steering 消息。` ) ) } } )
}
push ( { type : 'turn/end' , data : { turn , reason : { kind : 'completed' } } } )
}
@@ -128,14 +159,14 @@ function buildAlphaLog(): SessionEvent[] {
const toolTurn = ( turn : number , name : string , args : string , resultText : string ) : void = > {
const callId = ` fx-call- ${ turn } `
push ( { type : 'turn/start' , data : { turn , trigger : { kind : 'message' , source : { kind : 'user' } } } } )
push ( { type : 'user/message' , surfaceOp : 'append' , data : { content : text( ` 问题 ${ turn } : ${ name } 样本。 ` ) , source : { kind : 'user' } } } )
push ( { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( text ( ` 问题 ${ turn } : ${ name } 样本。 ` ) ) } )
push ( { type : 'step/start' , data : { turn , step : 0 } } )
push ( {
type : 'assistant/message' , surfaceOp : 'append' ,
data : { turn , step : 0 , content : [ { type : 'tool-call' , id : callId , name , arguments : args } as ContentBlock ] , provenance : { provider : 'fixture' , model : 'fx-1' } } ,
data : { turn , step : 0 , message : assistantMessage ( [ { type : 'tool-call' , id : callId , name , arguments : args } as ContentBlock ] ) } ,
} )
push ( { type : 'tool/call' , data : { turn , step : 0 , callId , name , arguments : args } } )
push ( { type : 'tool/result' , surfaceOp : 'append' , data : { turn , step : 0 , callId , content : text( resultText ) , isError : false } } )
push ( { type : 'tool/result' , surfaceOp : 'append' , data : { turn , step : 0 , message : toolResultMessage ( callId, text ( resultText ) , false) } } )
push ( { type : 'step/end' , data : { turn , step : 0 } } )
push ( { type : 'turn/end' , data : { turn , reason : { kind : 'completed' } } } )
}
@@ -156,11 +187,11 @@ function buildAlphaLog(): SessionEvent[] {
+ 'return { listing, demo }'
const args = JSON . stringify ( { code : program , description : 'Read the notes files and summarize' } )
push ( { type : 'turn/start' , data : { turn , trigger : { kind : 'message' , source : { kind : 'user' } } } } )
push ( { type : 'user/message' , surfaceOp : 'append' , data : { content : text( ` 问题 ${ turn } : run_code 样本。` ) , source : { kind : 'user' } } } )
push ( { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( text ( ` 问题 ${ turn } : run_code 样本。` ) ) } )
push ( { type : 'step/start' , data : { turn , step : 0 } } )
push ( {
type : 'assistant/message' , surfaceOp : 'append' ,
data : { turn , step : 0 , content : [ { type : 'tool-call' , id : callId , name : 'run_code' , arguments : args } as ContentBlock ] , provenance : { provider : 'fixture' , model : 'fx-1' } } ,
data : { turn , step : 0 , message : assistantMessage ( [ { type : 'tool-call' , id : callId , name : 'run_code' , arguments : args } as ContentBlock ] ) } ,
} )
push ( { type : 'tool/call' , data : { turn , step : 0 , callId , name : 'run_code' , arguments : args } } )
const dispatchPair = ( n : number , name : string , dispatchArgs : Record < string , unknown > , resultText : string , isError = false ) : void = > {
@@ -181,7 +212,7 @@ function buildAlphaLog(): SessionEvent[] {
dispatchPair ( 3 , 'read' , { path : 'notes/missing.txt' } , 'Error: ENOENT: notes/missing.txt not found' , true )
push ( {
type : 'tool/result' , surfaceOp : 'append' ,
data : { turn , step : 0 , callId , content : text( '{"listing":"demo.txt\\nnew-demo.txt","demo":"hello fixture\\n"}' ) , isError : false } ,
data : { turn , step : 0 , message : toolResultMessage ( callId, text ( '{"listing":"demo.txt\\nnew-demo.txt","demo":"hello fixture\\n"}' ) , false) } ,
} )
push ( { type : 'step/end' , data : { turn , step : 0 } } )
push ( { type : 'turn/end' , data : { turn , reason : { kind : 'completed' } } } )
@@ -255,13 +286,13 @@ function viewFor(event: SessionEvent, log: readonly SessionEvent[]): ToolEventVi
return view === undefined ? undefined : { for : 'call' , view }
}
if ( event . type === 'tool/result' ) {
const callId = String ( event . data . callId )
const callId = String ( event . data . message . source . callId)
for ( let i = log . length - 1 ; i >= 0 ; i -- ) {
const candidate = log [ i ]
/* v8 ignore next -- dense-array guard: i stays within [0, log.length),
so the undefined arm needs a sparse log no code path builds. */
if ( candidate !== undefined && candidate . type === 'tool/call' && String ( candidate . data . callId ) === callId ) {
const resultText = event . data . content . map ( b = > ( b . type === 'text' ? b . text : '' ) ) . join ( '' )
const resultText = event . data . message . content [ 0 ] . content. map ( b = > ( b . type === 'text' ? b . text : '' ) ) . join ( '' )
const view = presentResult ( candidate . data . name , candidate . data . arguments , resultText )
return view === undefined ? undefined : { for : 'result' , view }
}
@@ -271,18 +302,38 @@ function viewFor(event: SessionEvent, log: readonly SessionEvent[]): ToolEventVi
return undefined
}
/** Fold the latest fixture title into the host's control-frame projection . */
function titleFrameOf ( id : SessionId , log : readonly SessionEvent [ ] ) : Extract < MuxFrame , { type : 'session/title' } > | undefined {
const event = log . findLast ( item = > ( item as { type : string } ) . type === 'session/title' )
if ( event === undefined ) return undefined
const titleEvent = event as unknown as { seq : number ; time : number ; data : { title : string } }
return {
type : 'session/title' ,
sessionId : id ,
title : titleEvent.data.title ,
eventSeq : titleEvent.seq ,
updatedAt : titleEvent.time ,
/** Fixture parallel of the host's projection units: whole current values per key over the full log . */
function projectionValuesOf ( log : readonly SessionEvent [ ] ) : Record < string , unknown > {
const values : Record < string , unknown > = { }
const titleEvent = log . findLast ( item = > ( item as { type : string } ) . type === 'session/title' )
if ( titleEvent != = undefined ) {
values [ 'title' ] = ( titleEvent as unknown as { data : { title : string } } ) . data . title
}
// Always present (tool-todo unit composed): null when no plan stands.
values [ 'todos' ] = backscanTodos ( log ) ? ? null
return values
}
/** Host push-frame parallel: emit one session/projection frame per key the given event advanced. */
function projectionFramesOf ( id : SessionId , log : readonly SessionEvent [ ] , event : SessionEvent ) : Extract < MuxFrame , { type : 'session/projection' } > [ ] {
const type = ( event as { type : string } ) . type
if ( type === 'session/title' ) {
const values = projectionValuesOf ( log )
/* v8 ignore next -- the advancing title event is in the log, so the key is present. */
if ( ! Object . hasOwn ( values , 'title' ) ) return [ ]
return [ { type : 'session/projection' , sessionId : id , key : 'title' , value : values [ 'title' ] , seq : event.seq } ]
}
// Standing-plan fold: writes replace the list; turn/start clears it (null).
if ( type === 'todo/write' || type === 'turn/start' ) {
return [ {
type : 'session/projection' ,
sessionId : id ,
key : 'todos' ,
value : backscanTodos ( log ) ? ? null ,
seq : event.seq ,
} ]
}
return [ ]
}
/**
@@ -316,11 +367,16 @@ function pageOf(
return { events , hasMore : start > 0 }
}
/** Current todo projection over the full log (host parallel: latest todo/write, last write wins). */
/**
* Current plan projection over the full log (host parallel: latest todo/write
* with no later turn/start; a new turn retires the previous plan).
*/
function backscanTodos ( log : readonly SessionEvent [ ] ) : TodoItem [ ] | undefined {
for ( let i = log . length - 1 ; i >= 0 ; i -- ) {
const event = log [ i ]
if ( event ! == undefined && event . type === 'todo/write' ) return event . data . todos
if ( event = == undefined ) continue
if ( event . type === 'turn/start' ) return undefined
if ( event . type === 'todo/write' ) return event . data . todos
}
return undefined
}
@@ -543,10 +599,8 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
emitMux ( view === undefined
? { type : 'session/event' , sessionId : id , event }
: { type : 'session/event' , sessionId : id , event , view } )
if ( ( event as { type : string } ) . type === 'session/title' ) {
// The raw title is already in this log, so the latest-title fold must find it.
emitMux ( titleFrameOf ( id , log ) as Extract < MuxFrame , { type : 'session/title' } > )
}
// Host eager-drive parallel: a unit-advancing event pushes its finished value.
for ( const frame of projectionFramesOf ( id , log , event ) ) emitMux ( frame )
}
/** At most one in-flight replay per session; cancel clears it. */
@@ -573,7 +627,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
} ,
/** Log append + mux emit (the normal live path). */
appendUser ( id : string , msg : string ) : void {
append ( sid ( id ) , { type : 'user/message' , surfaceOp : 'append' , data : { content : text ( msg ) , source : { kind : 'user' } } } )
append ( sid ( id ) , { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( text ( msg ) ) } )
} ,
/** Append a later durable title revision through the normal raw-event + control-frame path. */
appendTitle ( id : string , title : string ) : void {
@@ -584,7 +638,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
/** Log append WITHOUT the mux emit: a frame lost in transit — history still serves it, the client must repull. */
appendSilent ( id : string , msg : string ) : void {
const log = logOf ( sid ( id ) )
log . push ( { type : 'user/message' , surfaceOp : 'append' , seq : log.length , time : Date.now ( ) , data : { content : text ( msg ) , source : { kind : 'user' } } } as unknown as SessionEvent )
log . push ( { type : 'user/message' , surfaceOp : 'append' , seq : log.length , time : Date.now ( ) , data : userMessage ( text ( msg ) ) } as unknown as SessionEvent )
} ,
/** End every open stream generator (client sees both streams close -> reconnect + resync path). */
breakStreams ( ) : void {
@@ -605,7 +659,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
replays . delete ( id )
const done = pieces . slice ( 0 , i ) . join ( '' )
append ( id , { type : 'assistant/chunk' , data : { turn , step , chunk : { type : 'block-end' , index : 0 , block : { type : 'text' , text : done } } } } )
append ( id , { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step , content : text( aborted ? ` ${ done } (已中断) ` : done ) , provenance : { provider : 'fixture' , model : 'fx-1' } } } )
append ( id , { type : 'assistant/message' , surfaceOp : 'append' , data : { turn , step , message : assistantMessage ( text( aborted ? ` ${ done } (已中断) ` : done ) ) } } )
append ( id , { type : 'step/end' , data : { turn , step } } )
append ( id , { type : 'turn/end' , data : { turn , reason : { kind : aborted ? 'cancelled' : 'completed' } } } )
setRunning ( id , false )
@@ -699,14 +753,18 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
const log = logs . get ( request . payload . sessionId ) ? ? [ ]
// Snapshot at request time, deliver after the transit delay (mirrors a real host under latency).
const page = pageOf ( log , request . payload . beforeSeq , request . payload . maxMessages ? ? 50 )
// Tail page carries the session-level todo projection (host parallel: full-log backscan).
const todos = request . payload . beforeSeq === undefined ? backscanTodos ( log ) : undefined
// Tail page carries the projections block (host parallel: one consistent
// cut over the registered units; asOfSeq = window tail seq, -1 on an
// empty log — the host's session.seq-1 convention).
const projections = request . payload . beforeSeq === undefined
? { asOfSeq : log.length - 1 , values : projectionValuesOf ( log ) }
: undefined
const doomed = failNextHistory
failNextHistory = false
const delay = historyDelayMs
if ( delay > 0 ) await new Promise ( resolve = > setTimeout ( resolve , delay ) )
if ( doomed ) throw new Error ( 'fixture: simulated history transport failure' )
return ok ( request , { . . . page , . . . todo s === undefined ? { } : { todo s } } )
return ok ( request , { . . . page , . . . projection s === undefined ? { } : { projection s } } )
} ,
models : request = > ok ( request , {
current : modelTargets.get ( request . payload . sessionId )
@@ -770,14 +828,14 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
// Steering: insert a steering message into the current turn; the replay continues.
/* v8 ignore next -- the ?? arm needs a missing counter, but a live replay implies a prior prompt already set it. */
const turn = ( nextTurn . get ( id ) ? ? 1 ) - 1
append ( id , { type : 'steering/message' , surfaceOp : 'append' , data : { turn , content , source : { kind : 'user' } } } )
append ( id , { type : 'steering/message' , surfaceOp : 'append' , data : { turn , message : userMessage ( content ) } } )
return ok ( request , { accepted : true as const } )
}
const turn = nextTurn . get ( id ) ? ? 0
nextTurn . set ( id , turn + 1 )
setRunning ( id , true )
append ( id , { type : 'turn/start' , data : { turn , trigger : { kind : 'message' , source : { kind : 'user' } } } } )
append ( id , { type : 'user/message' , surfaceOp : 'append' , data : { content , source : { kind : 'user' } } } )
append ( id , { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( content ) } )
startReply (
id ,
turn ,
@@ -841,6 +899,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
directoryTree . set ( target , [ ] )
return ok ( request , { path : target } )
} ,
openPath : request = > ok ( request , { opened : true as const } ) ,
} ,
workspace : {
list : request = > ok ( request , { items : workspaces.map ( w = > ( { . . . w } ) ) } ) ,
@@ -944,25 +1003,29 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
] ,
} )
} ,
// Pure admission, mirroring the host: an admitted command logs the
// command/run + command/done lifecycle pair (mux-broadcast by append),
// and the response only reports resolution.
execute : ( request ) = > {
const missing = requireSession ( request )
if ( missing !== undefined ) return missing
const line = request . payload . line . trim ( )
const match = /^\/(\S+)(?:\s+(.*))?$/ . exec ( line )
const id = request . payload . sessionId
// Structured split mirroring the host parser: name + verbatim rawInput
// (separator whitespace included) — the run payload carries no line.
const match = /^\/(\S+)((?:\s.*)?)$/ . exec ( request . payload . line . trim ( ) )
const name = match ? . [ 1 ]
if ( name === 'compact' || name === 'echo' ) {
return ok ( request , {
matched : true as const ,
result : { kind : 'success' as const , text : name === 'echo' ? ( match ? . [ 2 ] ? ? '' ) : 'fixture: 已压缩( 假动作) ' } ,
} )
const args = match ? . [ 2 ] ? ? ''
const outcomes : Record < string , string > = {
compact : 'fixture: 已压缩( 假动作) ' ,
echo : args.trim ( ) ,
'goal-fixture' : ` fixture: goal 已设置( ${ id } ) ` ,
}
if ( name === 'goal-fixture' ) {
return ok ( request , {
matched : true as const ,
result : { kind : 'success' as const , text : ` fixture: goal 已设置( ${ request . payload . sessionId } ) ` } ,
} )
}
return ok ( request , { matched : false as const } )
const text = name === undefined ? undefined : outcomes [ name ]
if ( name === undefined || text === undefined ) return ok ( request , { matched : false as const } )
const commandId = ` fx-cmd- ${ logOf ( id ) . length } ` as CommandId
append ( id , { type : 'command/run' , data : { commandId , name , args , source : { kind : 'user' } } } )
append ( id , { type : 'command/done' , data : { commandId , kind : 'success' , . . . text === '' ? { } : { text } } } )
return ok ( request , { matched : true as const , commandId } )
} ,
} ,
skills : {
@@ -985,9 +1048,13 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
// Open baseline: subscribed sessions + pending interactions replayed with stable rpcIds.
for ( const s of sessions ) {
if ( ! s . running ) continue
conn . push ( { rpcId : mint ( ) , payload : { type : 'session/subscribed' , sessionId : s.sessionId , lastSeq : ( logs . get ( s . sessionId ) ? . length ? ? 0 ) - 1 } } )
const title = titleFrameOf ( s . sessionId , logs . get ( s . sessionId ) ? ? [ ] )
if ( title !== undefined ) conn . push ( { rpcId : mint ( ) , payload : title } )
const log = logs . get ( s . sessionId ) ? ? [ ]
conn . push ( { rpcId : mint ( ) , payload : { type : 'session/subscribed' , sessionId : s.sessionId , lastSeq : log.length - 1 } } )
// Post-subscribe projection baseline (host parallel: recomputed unit values ride push frames).
const values = projectionValuesOf ( log )
for ( const key of Object . keys ( values ) ) {
conn . push ( { rpcId : mint ( ) , payload : { type : 'session/projection' , sessionId : s.sessionId , key , value : values [ key ] , seq : log.length - 1 } } )
}
}
conn . push ( {
rpcId : pendingApprovalRpcId ,
@@ -1094,6 +1161,7 @@ export class FixtureApiClient extends AbstractApiClient {
case 'host.pickDirectory' : return this . api . host . pickDirectory ( request , new AbortController ( ) . signal )
case 'host.listDirectory' : return this . api . host . listDirectory ( request )
case 'host.createDirectory' : return this . api . host . createDirectory ( request )
case 'host.openPath' : return this . api . host . openPath ( request , new AbortController ( ) . signal )
case 'workspace.list' : return this . api . workspace . list ( request )
case 'workspace.create' : return this . api . workspace . create ( request )
case 'workspace.rename' : return this . api . workspace . rename ( request )