@@ -2,15 +2,34 @@
// RpcRequest<P> and returns RpcResponse<T> (echoing the rpcId); streams yield RpcRequest<frame>
// (the fixture IS the fake server, so it mints frame rpcIds); root respond takes ClientResponse
// and returns RpcReceipt. fx-alpha carries a hand-built history script (60 turns, pageable);
// prompt triggers a chunked streaming replay; cancel stops the replay; one resident pending
// approval (placeholder-card material, subscribed-baseline-replay semantics: stable rpcId reuse) .
// 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 } 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 ,
RpcRequest , RpcResponse , RpcResult , ServerRequest , ServerResponse , SessionSummary ,
ToolCallView , ToolEventView , ToolResultView , Goal View,
ModelTarget , RpcRequest, RpcResponse , RpcResult , ServerRequest , ServerResponse , SessionSummary ,
ToolCallView , ToolEventView , ToolResultView , WorkspaceId , Workspace View,
} from './api.ts'
import type { RequestPayload , ResponseValue , RpcMethodMap } from '@deepseek-ai/dsh-host-apiproxy/api'
import { AbstractApiClient , RpcId } from './api.ts'
@@ -24,6 +43,62 @@ 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' ,
'' ,
'Assistant output renders **strong text**, *emphasis*, and `inline code`.' ,
'' ,
'- first item' ,
' - nested item' ,
'' ,
'| Surface | State |' ,
'| --- | --- |' ,
'| history | rendered |' ,
'| streaming | stable |' ,
'' ,
'[DeepSeek](https://www.deepseek.com)' ,
'' ,
'```ts' ,
'const markdown = true' ,
'```' ,
] . join ( '\n' )
const USER_MARKDOWN_LITERAL = '用户字面量:# 不渲染 `code` [link](https://example.com)'
const DEEPSEEK_REASONING = {
efforts : [
{ id : 'off' , name : 'Off' } ,
{ id : 'high' , name : 'High' } ,
{ id : 'max' , name : 'Max' } ,
] ,
defaultEffort : 'high' ,
}
const OPENAI_REASONING = {
efforts : [
{ id : 'off' , name : 'Off' } ,
{ id : 'medium' , name : 'Medium' } ,
{ id : 'high' , name : 'High' } ,
{ id : 'max' , name : 'Max' } ,
] ,
defaultEffort : 'medium' ,
}
function sid ( id : string ) : SessionId {
return id as SessionId
}
@@ -40,54 +115,124 @@ function buildAlphaLog(): SessionEvent[] {
}
for ( let turn = 0 ; turn < 60 ; turn ++ ) {
push ( { type : 'turn/start' , data : { turn , trigger : { kind : 'message' , source : { kind : 'user' } } } } )
push ({ type : 'user/message' , surfaceOp : 'append' , data : { content : text ( ` 问题 ${ turn } : fixture 历史消息,用于翻页与渲染验收。` ) , source : { kind : 'user' } } } )
const userSeq = push ( {
type : 'user/message' , surfaceOp : 'append' ,
data : userMessage ( text ( turn === 59 ? USER_MARKDOWN_LITERAL : ` 问题 ${ turn } : fixture 历史消息,用于翻页与渲染验收。` ) ) ,
} )
if ( turn === 0 ) {
push ( {
type : 'session/title' ,
data : { title : 'Fixture 历史会话' , messageSeqs : [ userSeq ] , source : { kind : 'fallback' } } ,
} )
}
if ( turn % 9 === 4 ) {
push ( { type : 'context /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
const withReasoning = turn % 3 === 1
const blocks : ContentBlock [ ] = [ ]
if ( withReasoning ) blocks . push ( { type : 'reasoning' , text : ` 思考过程 ${ turn } :这是一段可折叠的 reasoning 内容。 ` } )
blocks . push ( { type : 'text' , text : ` 回答 ${ turn } :这是 fixture 生成的历史回复正文。 ` } )
blocks . push ( { type : 'text' , text : turn === 59 ? MARKDOWN_FIXTURE : ` 回答 ${ turn } :这是 fixture 生成的历史回复正文。 ` } )
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' } } } )
}
// Three view-sample turns (60-62) fo r the tool-card wire acceptance: one per built-in card
// type. `echo` above stays presenter-less on purpose — it is the no-view fallback sample.
// Three view-sample turns (60-62) cove r the built-in card types. The real filesystem names in
// turns 62-63 also exercise their dedicated generic-row icon/title/path summaries. `echo` above
// stays presenter-less as the unknown fallback.
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' } } } )
}
toolTurn ( 60 , 'fx-bash' , '{"command":"ls -la","cwd":"/tmp/fixture"}' , 'total 2\ndrwxr-xr-x fixture\n-rw-r--r-- demo.txt' )
toolTurn ( 61 , 'fx-write' , '{"path":"notes/demo.txt","content":"hello fixture\\n"}' , 'wrote notes/demo.txt' )
toolTurn ( 62 , 'fx-note ' , '{"note":"三型卡验收样本 "}' , '已记录 ' )
toolTurn ( 62 , 'edit ' , '{"file_path":"notes/demo.txt","old_string":"hello","new_string":"hello fixture "}' , '已编辑 ' )
toolTurn ( 63 , 'write' , '{"file_path":"notes/new-demo.txt","content":"hello fixture\\n"}' , '已写入' )
// Turn 64: one run_code turn with three logged sub-dispatches — the Code
// Mode acceptance surface (parent code row + nested native-identical rows,
// including an isError sub-call and a bash sub-call that must hit the same
// keyed registration a top-level bash row uses).
{
const turn = 64
const callId = ` fx-call- ${ turn } `
const program = 'const listing = await tools.bash({ command: "ls notes", description: "List notes" })\n'
+ 'const demo = await tools.read({ path: "notes/demo.txt" })\n'
+ 'await tools.read({ path: "notes/missing.txt" }).catch(() => "tolerated")\n'
+ '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 : userMessage ( text ( ` 问题 ${ turn } : run_code 样本。` ) ) } )
push ( { type : 'step/start' , data : { turn , step : 0 } } )
push ( {
type : 'assistant/message' , surfaceOp : 'append' ,
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 = > {
push ( {
type : 'tool/code-dispatch-start' ,
data : { parentCallId : callId , subCallId : ` ${ callId } :code: ${ n } ` , name , arguments : dispatchArgs } ,
} )
push ( {
type : 'tool/code-dispatch' ,
data : {
parentCallId : callId , subCallId : ` ${ callId } :code: ${ n } ` , name ,
arguments : dispatchArgs , isError , content : [ { type : 'text' , text : resultText } ] ,
} ,
} )
}
dispatchPair ( 1 , 'bash' , { command : 'ls notes' , description : 'List notes' } , 'demo.txt\nnew-demo.txt' )
dispatchPair ( 2 , 'read' , { path : 'notes/demo.txt' } , 'hello fixture\n' )
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 , 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' } } } )
}
// Turn 65: todo_write sample — the TodoRow toolview in the flow plus the
// todo/write snapshot event feeding the TodoPanel plan strip.
const fixtureTodos = [
{ content : '梳理需求' , status : 'completed' } ,
{ content : '实现 fixture 样本' , status : 'in_progress' } ,
{ content : '浏览器验收' , status : 'pending' } ,
]
const todoArgs = JSON . stringify ( { todos : fixtureTodos } )
toolTurn ( 65 , 'todo_write' , todoArgs , 'Updated todo list: 1 pending, 1 in progress, 1 completed.' )
// The real tool appends the snapshot mid-execution — between tool/call and
// tool/result — so the fixture reproduces that exact ordering (the last
// toolTurn events run ... tool/call, tool/result, step/end, turn/end).
const callIndex = events . length - 4
const callTime = events [ callIndex ] ? . time as number
events . splice ( callIndex + 1 , 0 , { type : 'todo/write' , time : callTime + 400 , data : { todos : fixtureTodos } } )
events . forEach ( ( e , i ) = > { e . seq = i } )
return events as unknown as SessionEvent [ ]
}
@@ -112,8 +257,10 @@ function presentCall(name: string, argsRaw: string): ToolCallView | undefined {
card : 'diff' , title : ` Write ${ str ( args . path ) } ` ,
diffs : [ { path : str ( args . path ) , oldText : null , newText : str ( args . content ) } ] ,
}
case 'fx-note ' :
return { card : 'generic' , title : '记录笔记' , kind : 'edit' , rawInput : args }
case 'edit ' :
return { card : 'generic' , title : ` Edit ${ str ( args . file_path ) } ` , kind : 'edit' , rawInput : args }
case 'write' :
return { card : 'generic' , title : ` Write ${ str ( args . file_path ) } ` , kind : 'edit' , rawInput : args }
default :
return undefined // echo et al: the documented no-view fallback path
}
@@ -139,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 }
}
@@ -155,6 +302,40 @@ function viewFor(event: SessionEvent, log: readonly SessionEvent[]): ToolEventVi
return undefined
}
/** 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 [ ]
}
/**
* Message-boundary paging (mirrors the host's paging contract): count
* maxMessages messages
@@ -186,10 +367,38 @@ function pageOf(
return { events , hasMore : start > 0 }
}
/**
* 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 ) continue
if ( event . type === 'turn/start' ) return undefined
if ( event . type === 'todo/write' ) return event . data . todos
}
return undefined
}
interface StreamConn < F > {
push ( envelope : RpcRequest < F > ) : void
}
/** Deterministic fixture branches used by keyless Web assembly tests. */
export interface FixtureOptions {
/** Start with no real Workspace or Session. */
empty? : boolean
/** Reject every prompt before appending its user event. */
rejectPrompt? : boolean
/** Publish the Session but fail its Workspace account write. */
failWorkspaceAttach? : boolean
/** Publish and frame the Session, then throw instead of returning create. */
dropSessionCreateResponse? : boolean
/** Order of the two successful create frames. */
createFrameOrder ? : 'session-first' | 'workspace-first'
}
/** Inbox pump shared by both stream generators (FrameQueue pattern: ONE abort listener hung
* outside the loop — a per-iteration {once:true} listener never fires for non-final rounds and
* piles up for the stream's lifetime, audit C5). breakNow force-ends the stream without the
@@ -234,26 +443,76 @@ class FxInbox<F> implements StreamConn<F> {
/**
* In-memory fake host: fx-alpha carries history and replay scripts; fx-beta is fx-alpha's child session (lineage indent material).
* @param options - fixture branches for empty state and failure timing.
* @returns an ApiProxy backed entirely by in-memory state — no host process, no network.
*/
export function createFixtureApi ( ) : ApiProxy {
const sessions : SessionSummary [ ] = [
{ sessionId : sid ( 'fx-alpha' ) , updatedAt : Date.now ( ) , running : true , cwd : '/tmp/fixture' } ,
{ sessionId : sid ( 'fx-bet a' ) , updatedAt : Date.now ( ) - 60 _000 , running : fals e, parentSessionId : sid ( 'fx-alpha' ) , cwd : '/tmp/fixture' } ,
{ sessionId : sid ( 'fx-gamm a' ) , updatedAt : Date.now ( ) - 12 0_000 , running : false , cwd : '/tmp/fixture' } ,
export function createFixtureApi ( options : FixtureOptions = { } ) : ApiProxy {
// The resident fixture sessions all carry history, so none of them is blank.
const sessions : SessionSummary [ ] = options . empty ? [ ] : [
{ sessionId : sid ( 'fx-alph a' ) , updatedAt : Date.now ( ) , running : tru e, blank : false , cwd : '/tmp/fixture' } ,
{ sessionId : sid ( 'fx-bet a' ) , updatedAt : Date.now ( ) - 6 0_000 , running : false , blank : false , parentSessionId : sid ( 'fx-alpha' ) , cwd : '/tmp/fixture' } ,
{ sessionId : sid ( 'fx-gamma' ) , updatedAt : Date.now ( ) - 120 _000 , running : false , blank : false , cwd : '/tmp/fixture' } ,
]
const logs = new Map < SessionId , SessionEvent [ ] > ( [ [ sid ( 'fx-alpha' ) , buildAlphaLog ( ) ] ] )
const fixtureGoal : GoalView = {
id : 'fx-goal-1' as GoalView [ 'id' ] , revision : 1 , objective : 'Ship the fixture goal bar' ,
phase : 'active' , maxGoalRounds : 4 , roundsStarted : 1 , createdAt : 1 , updatedAt : 2 ,
activation : 'armed' ,
}
const modelTargets = new Map < SessionId , ModelTarget > ( sessions . map ( session = > [
session . sessionId ,
{ provider : 'deepseek' , model : 'deepseek-v4-flash' } ,
] ) )
const nextTurn = new Map < SessionId , number > ( [ [ sid ( 'fx-alpha' ) , 60 ] ] )
let nextSession = 1
let nextRpc = 1
let attachedSessions = options . empty ? 0 : 1
// Workspace entities mirroring the host registry: the fixture sessions all
// live under one workspace, whose account carries them in attach order.
const wid = ( raw : string ) : WorkspaceId = > raw as WorkspaceId
const fixtureEpoch = new Date ( Date . now ( ) - 300 _000 ) . toISOString ( )
const workspaces : WorkspaceView [ ] = options . empty ? [ ] : [ {
workspaceId : wid ( 'fx-ws-fixture' ) ,
path : '/tmp/fixture' ,
title : 'fixture' ,
sessionIds : [ sid ( 'fx-alpha' ) , sid ( 'fx-beta' ) , sid ( 'fx-gamma' ) ] ,
createdAt : fixtureEpoch ,
updatedAt : fixtureEpoch ,
} ]
let nextWorkspace = 1
const mint = ( ) : ReturnType < typeof RpcId > = > RpcId ( ` fx-rpc- ${ nextRpc ++ } ` )
/** Resident pending approval (stable rpcId: every mux open replays the same id, matching host replay semantics). */
const pendingApprovalRpcId = mint ( )
const pendingQuestionRpcId = mint ( )
let questionPending = true
const fixtureQuestions : Extract < MuxFrame , { type : 'question/requested' } > [ 'questions' ] = [
{
id : 'harness-profile' ,
header : '偏好' ,
question : '你现在更想招哪类 Agent/Harness 候选人?' ,
options : [
{ label : '工程落地型 (Recommended)' , description : '更看重能直接做 runtime、tool executor、sandbox、trace 和线上问题排查。' } ,
{ label : '研究潜力型' , description : '更看重 Agent 理解、训练评测思路和长期成长空间。' } ,
{ label : '均衡型' , description : '同时要求工程能力和 Agent 认知,但可能筛选门槛更高。' } ,
] ,
} ,
{
id : 'work-mode' ,
header : '方式' ,
question : '你希望候选人优先展示哪种工作方式?' ,
options : [
{ label : '先做小型原型 (Recommended)' , description : '用可运行结果尽快验证关键假设。' } ,
{ label : '先写完整设计' , description : '先收敛边界、协议和风险,再开始实现。' } ,
] ,
} ,
{
id : 'signals' ,
header : '信号' ,
question : '哪些面试信号最重要?' ,
detail : '按当前招聘目标选择;跳过则视为不设偏好。' ,
multiSelect : true ,
options : [
{ label : '系统设计' } ,
{ label : '代码质量' } ,
{ label : 'Agent 产品判断' } ,
] ,
} ,
]
const muxConns = new Set < StreamConn < MuxFrame > > ( )
const hostConns = new Set < StreamConn < HostFrame > > ( )
@@ -273,6 +532,16 @@ export function createFixtureApi(): ApiProxy {
}
const summaryOf = ( id : SessionId ) : SessionSummary | undefined = > sessions . find ( s = > s . sessionId === id )
/** Shared session guard for sessionId-addressed catalog routes: the error
* response when the session is unknown, undefined when it exists. */
const requireSession = ( request : RpcRequest < { sessionId : SessionId } > ) : Promise < RpcResponse < never > > | undefined = > {
if ( summaryOf ( request . payload . sessionId ) !== undefined ) return undefined
return err < { sessionId : SessionId } , never > ( request , {
code : 'session-not-found' ,
message : ` no session ${ request . payload . sessionId } ` ,
details : { sessionId : request.payload.sessionId } ,
} )
}
const setRunning = ( id : SessionId , running : boolean ) : void = > {
const summary = summaryOf ( id )
if ( summary === undefined || summary . running === running ) return
@@ -299,6 +568,8 @@ export function createFixtureApi(): ApiProxy {
emitMux ( view === undefined
? { type : 'session/event' , sessionId : id , event }
: { type : 'session/event' , sessionId : id , event , view } )
// 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. */
@@ -325,12 +596,18 @@ export function createFixtureApi(): 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 {
const log = logOf ( sid ( id ) )
const messageSeqs = log . filter ( event = > event . type === 'user/message' ) . map ( event = > event . seq )
append ( sid ( id ) , { type : 'session/title' , data : { title , messageSeqs , source : { kind : 'provider' , provider : 'fixture' } } } )
} ,
/** 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 {
@@ -344,14 +621,14 @@ export function createFixtureApi(): ApiProxy {
const step = 0
append ( id , { type : 'step/start' , data : { turn , step } } )
append ( id , { type : 'assistant/chunk' , data : { turn , step , chunk : { type : 'block-start' , index : 0 , blockType : 'text' } } } )
/* v8 ignore next -- the ?? arm needs a null match, but replyText is never empty (prompt always prefixes 回声) . */
const pieces = replyText . match ( / . { 1 , 6 } / g u ) ? ? [ r e p l y T e x t ]
/* v8 ignore next -- the ?? arm needs a null match, but every fixture reply is non-empty . */
const pieces = replyText . match ( / [ \ s \ S ] { 1 , 6 } / g u ) ? ? [ r e p l y T e x t ]
let i = 0
const finish = ( aborted : boolean ) : void = > {
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 )
@@ -372,24 +649,132 @@ export function createFixtureApi(): ApiProxy {
return {
sessions : {
list : request = > ok ( request , { items : [ . . . sessions ] . sort ( ( a , b ) = > b . updatedAt - a . updatedAt ) } ) ,
create : ( request ) = > {
create : async ( request ) = > {
const workspace = request . payload . workspaceId === undefined
? undefined
: workspaces . find ( w = > w . workspaceId === request . payload . workspaceId )
if ( request . payload . workspaceId !== undefined && workspace === undefined ) {
return err ( request , {
code : 'workspace-not-found' ,
message : ` no workspace ${ request . payload . workspaceId } ` ,
details : { workspaceId : request.payload.workspaceId } ,
} )
}
const cwd = workspace ? . path ? ? request . payload . cwd ? ? '/tmp/fixture'
const requestedId = request . payload . sessionId
const attachWorkspace = ( sessionId : SessionId ) : void = > {
/* v8 ignore next -- callers enter only when a target Workspace exists. */
if ( workspace === undefined || workspace . sessionIds . includes ( sessionId ) ) return
workspace . sessionIds = [ sessionId , . . . workspace . sessionIds ]
workspace . updatedAt = new Date ( ) . toISOString ( )
emitHost ( { type : 'host/workspace-changed' , workspace : { . . . workspace } } )
}
const attachFailure = (
sessionId : SessionId ,
workspaceId : WorkspaceId ,
) : Promise < RpcResponse < { sessionId : SessionId } > > = > err ( request , {
code : 'workspace-attach-failed' as const ,
message : ` fixture rejected Workspace attachment for ${ sessionId } ` ,
details : { sessionId , workspaceId } ,
} )
if ( requestedId !== undefined ) {
const existing = summaryOf ( requestedId )
if ( existing !== undefined ) {
if ( existing . cwd !== cwd ) {
return err ( request , {
code : 'session-conflict' ,
message : ` session ${ requestedId } already uses ${ existing . cwd ? ? 'no cwd' } ` ,
details : { sessionId : requestedId , requestedCwd : cwd , . . . existing . cwd === undefined ? { } : { existingCwd : existing.cwd } } ,
} )
}
if ( workspace !== undefined && ! workspace . sessionIds . includes ( requestedId ) ) {
if ( options . failWorkspaceAttach ) return attachFailure ( requestedId , workspace . workspaceId )
attachWorkspace ( requestedId )
}
return ok ( request , { sessionId : requestedId } )
}
}
const created : SessionSummary = {
sessionId : sid ( ` fx- ${ nextSession ++ } ` ) , updatedAt : Date.now ( ) , running : false , cwd : '/tmp/fixture' ,
sessionId : requestedId ? ? sid( ` fx- ${ nextSession ++ } ` ) , updatedAt : Date.now ( ) , running : false , blank : true , cwd ,
}
sessions . push ( created )
emitHost ( { type : 'host/session-added' , sessionId : created.sessionId } )
modelTargets . set ( created . sessionId , { provider : 'deepseek' , model : 'deepseek-v4-flash' } )
attachedSessions += 1
const emitSession = ( ) : void = > {
// Mirrors the host: the frame fires at creation, so blank is constantly true.
emitHost ( { type : 'host/session-added' , sessionId : created.sessionId , blank : true , cwd } )
}
if ( workspace !== undefined && options . failWorkspaceAttach ) {
emitSession ( )
return attachFailure ( created . sessionId , workspace . workspaceId )
}
if ( workspace !== undefined && options . createFrameOrder === 'workspace-first' ) {
attachWorkspace ( created . sessionId )
emitSession ( )
} else {
emitSession ( )
if ( workspace !== undefined ) attachWorkspace ( created . sessionId )
}
if ( options . dropSessionCreateResponse ) throw new Error ( 'fixture: dropped session.create response after publication' )
return ok ( request , { sessionId : created.sessionId } )
} ,
history : async ( request ) = > {
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 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 )
return ok ( request , { . . . page , . . . projections === undefined ? { } : { projections } } )
} ,
models : request = > ok ( request , {
current : modelTargets.get ( request . payload . sessionId )
? ? { provider : 'deepseek' , model : 'deepseek-v4-flash' } ,
groups : [
{
id : 'deepseek' ,
name : 'DeepSeek' ,
models : [
{
id : 'deepseek-v4-flash' ,
name : 'DeepSeek-V4-Flash' ,
description : '快速响应' ,
reasoning : DEEPSEEK_REASONING ,
} ,
{
id : 'deepseek-v4-pro' ,
name : 'DeepSeek-V4-Pro' ,
description : '复杂任务' ,
reasoning : DEEPSEEK_REASONING ,
} ,
] ,
} ,
{
id : 'openai' ,
name : 'OpenAI' ,
models : [ { id : 'gpt-5' , name : 'GPT-5' , reasoning : OPENAI_REASONING } ] ,
} ,
] ,
failures : [ ] ,
} ) ,
selectModel : ( request ) = > {
const selected : ModelTarget = {
provider : request.payload.provider ,
model : request.payload.model ,
. . . request . payload . reasoningEffort === undefined
? { }
: { reasoningEffort : request.payload.reasoningEffort } ,
}
modelTargets . set ( request . payload . sessionId , selected )
return ok ( request , { selected } )
} ,
prompt : ( request ) = > {
const { sessionId : id , mode , content } = request . payload
@@ -397,21 +782,42 @@ export function createFixtureApi(): ApiProxy {
if ( summary === undefined ) {
return err ( request , { code : 'session-not-found' , message : ` no session ${ id } ` , details : { sessionId : id } } )
}
if ( options . rejectPrompt ) {
return err ( request , {
code : 'agent-busy' ,
message : 'fixture: prompt rejected before acceptance' ,
details : { reason : 'fixture-prompt-rejection' } ,
} )
}
summary . updatedAt = Date . now ( )
// First accepted prompt appends events: the summary stops being blank.
summary . blank = false
const userText = content . map ( b = > ( b . type === 'text' ? b . text : '' ) ) . join ( '' )
if ( mode === 'steer' && replays . has ( id ) ) {
// 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' } } } )
startReply ( id , turn , ` 回声: ${ userText } 。这是 fixture 的流式回复,用于验证打字机增长与定稿切换。 ` )
append ( id , { type : 'user/message' , surfaceOp : 'append' , data : userMessage ( content ) } )
startReply (
id ,
turn ,
userText === 'render markdown'
? MARKDOWN_FIXTURE
: userText === 'report model'
? ( ( ) = > {
const target = modelTargets . get ( id )
return ` 当前模型: ${ target ? . provider ? ? 'unknown' } / ${ target ? . model ? ? 'unknown' } `
+ ( target ? . reasoningEffort === undefined ? '' : ` · 推理等级: ${ target . reasoningEffort } ` )
} ) ( )
: ` 回声: ${ userText } 。这是 fixture 的流式回复,用于验证打字机增长与定稿切换。 ` ,
)
return ok ( request , { accepted : true as const } )
} ,
cancel : ( request ) = > {
@@ -426,10 +832,149 @@ export function createFixtureApi(): ApiProxy {
} ,
} ,
host : {
describe : request = > ok ( request , { version : '0.0.0-fixture' , cwd : '/tmp/fixture' , attachedSessions : 1 } ) ,
describe : request = > ok ( request , { version : '0.0.0-fixture' , cwd : '/tmp/fixture' , attachedSessions } ) ,
pickDirectory : request = > ok ( request , { path : null } ) ,
openPath : request = > ok ( request , { opened : true as const } ) ,
} ,
workspace : {
list : request = > ok ( request , { items : workspaces.map ( w = > ( { . . . w } ) ) } ) ,
create : ( request ) = > {
const { path , name } = request . payload
const target = path ? ? ` /tmp/fixture-workspaces/ ${ name ? ? '' } `
const existing = workspaces . find ( w = > w . path === target )
if ( existing !== undefined ) return ok ( request , { workspace : { . . . existing } , created : false } )
const now = new Date ( ) . toISOString ( )
const created : WorkspaceView = {
workspaceId : wid ( ` fx-ws- ${ nextWorkspace ++ } ` ) ,
path : target ,
title : name ? ? target . split ( '/' ) . filter ( Boolean ) . at ( - 1 ) ? ? target ,
sessionIds : [ ] ,
createdAt : now ,
updatedAt : now ,
}
workspaces . unshift ( created )
emitHost ( { type : 'host/workspace-changed' , workspace : { . . . created } } )
return ok ( request , { workspace : { . . . created } , created : true } )
} ,
rename : ( request ) = > {
const { workspaceId , title } = request . payload
const workspace = workspaces . find ( w = > w . workspaceId === workspaceId )
if ( workspace === undefined ) {
return err ( request , {
code : 'workspace-not-found' ,
message : ` no workspace ${ workspaceId } ` ,
details : { workspaceId } ,
} )
}
const trimmed = title . trim ( )
if ( trimmed !== workspace . title ) {
if ( workspaces . some ( w = > w . workspaceId !== workspaceId && w . title === trimmed ) ) {
return err ( request , {
code : 'workspace-name-conflict' ,
message : ` workspace name ' ${ trimmed } ' is already in use ` ,
details : { name : trimmed } ,
} )
}
workspace . title = trimmed
workspace . updatedAt = new Date ( ) . toISOString ( )
emitHost ( { type : 'host/workspace-changed' , workspace : { . . . workspace } } )
}
return ok ( request , { workspace : { . . . workspace } } )
} ,
delete : ( request ) = > {
const { workspaceId } = request . payload
const index = workspaces . findIndex ( workspace = > workspace . workspaceId === workspaceId )
if ( index === - 1 ) {
return err ( request , {
code : 'workspace-not-found' ,
message : ` no workspace ${ workspaceId } ` ,
details : { workspaceId } ,
} )
}
workspaces . splice ( index , 1 )
emitHost ( { type : 'host/workspace-removed' , workspaceId } )
return ok ( request , { deleted : true as const } )
} ,
insertSessionBefore : ( request ) = > {
const { workspaceId , sessionId , beforeSessionId } = request . payload
const workspace = workspaces . find ( w = > w . workspaceId === workspaceId )
if ( workspace === undefined ) {
return err ( request , {
code : 'workspace-not-found' ,
message : ` no workspace ${ workspaceId } ` ,
details : { workspaceId } ,
} )
}
if ( ! workspace . sessionIds . includes ( sessionId )
|| ( beforeSessionId !== undefined && ! workspace . sessionIds . includes ( beforeSessionId ) ) ) {
return err ( request , {
code : 'workspace-move-invalid' ,
message : ` session or anchor is not accounted by workspace ${ workspaceId } ` ,
details : { workspaceId , sessionId , . . . beforeSessionId === undefined ? { } : { beforeSessionId } } ,
} )
}
const without = workspace . sessionIds . filter ( id = > id !== sessionId )
const at = beforeSessionId === undefined ? without.length : without.indexOf ( beforeSessionId )
const sessionIds = [ . . . without . slice ( 0 , at ) , sessionId , . . . without . slice ( at ) ]
if ( ! sessionIds . every ( ( id , index ) = > id === workspace . sessionIds [ index ] ) ) {
workspace . sessionIds = sessionIds
workspace . updatedAt = new Date ( ) . toISOString ( )
emitHost ( { type : 'host/workspace-changed' , workspace : { . . . workspace } } )
}
return ok ( request , { workspace : { . . . workspace } } )
} ,
} ,
commands : {
// The catalog mirrors one session's effective view (every fixture
// session has an agent, like the real host).
list : ( request ) = > {
const missing = requireSession ( request )
if ( missing !== undefined ) return missing
return ok ( request , {
commands : [
{ name : 'compact' , description : 'fixture: 压缩当前会话上下文' } ,
{ name : 'echo' , description : 'fixture: 回显参数' , input : { hint : 'text to echo' } } ,
{ name : 'goal-fixture' , description : 'fixture: 目标样本命令' , input : { hint : '<objective>' } } ,
] ,
} )
} ,
// 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 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 ]
const args = match ? . [ 2 ] ? ? ''
const outcomes : Record < string , string > = {
compact : 'fixture: 已压缩( 假动作) ' ,
echo : args.trim ( ) ,
'goal-fixture' : ` fixture: goal 已设置( ${ id } ) ` ,
}
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 : {
list : ( request ) = > {
const missing = requireSession ( request )
if ( missing !== undefined ) return missing
return ok ( request , {
skills : [
{ name : 'fixture-demo' , description : 'fixture 技能样本' , whenToUse : '仅供 UI 目录渲染验收' } ,
] ,
} )
} ,
} ,
goals : {
get : request = > ok ( request , { goal : request.payload.sessionId === sid ( 'fx-alpha' ) ? fixtureGoal : null } ) ,
create : request = > err ( request , { code : 'internal' , message : 'fixture: goals not implemented' , details : { } } ) ,
edit : request = > err ( request , { code : 'internal' , message : 'fixture: goals not implemented' , details : { } } ) ,
pause : request = > err ( request , { code : 'internal' , message : 'fixture: goals not implemented' , details : { } } ) ,
@@ -443,10 +988,16 @@ export function createFixtureApi(): ApiProxy {
muxConns . add ( conn )
const breakNow = ( ) : void = > { conn . breakNow ( ) }
streamBreakers . add ( breakNow )
// Open baseline: subscribed for attached (running) sessions + pending approval replay ( stable rpcId) .
// 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 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 ,
@@ -456,6 +1007,14 @@ export function createFixtureApi(): ApiProxy {
toolName : 'dangerous_tool' , reason : 'fixture 常驻占位审批(可见不可答)' ,
} ,
} )
if ( questionPending ) {
conn . push ( {
rpcId : pendingQuestionRpcId ,
payload : {
type : 'question/requested' , sessionId : sid ( 'fx-alpha' ) , questions : fixtureQuestions ,
} ,
} )
}
try {
yield * conn . drain ( signal )
} finally {
@@ -485,9 +1044,16 @@ export function createFixtureApi(): ApiProxy {
} ,
} ,
respond ( message : ClientResponse ) : Promise < RpcReceipt > {
// The v1 UI never answers (PendingCard is visible but not answerable); implemented for type completeness, always not-pending.
void message
return Promise . resolve ( { accepted : false , reason : 'not-pending' } )
if ( ! questionPending || message . rpcId !== pendingQuestionRpcId ) {
return Promise . resolve ( { accepted : false , reason : 'not-pending' } )
}
questionPending = false
emitMux ( {
type : 'question/resolved' , sessionId : sid ( 'fx-alpha' ) ,
questionRpcId : pendingQuestionRpcId ,
outcome : message.result.ok ? 'answered' : 'cancelled' ,
} )
return Promise . resolve ( { accepted : true } )
} ,
}
}
@@ -500,7 +1066,12 @@ export function createFixtureApi(): ApiProxy {
* to the isomorphic pipeline (InProcessApiClient over toFetchHandler(fixtureImpl)).
*/
export class FixtureApiClient extends AbstractApiClient {
private readonly api = createFixtureApi ( )
private readonly api : ApiProxy
constructor ( ) {
super ( )
this . api = createFixtureApi ( fixtureOptionsFromLocation ( ) )
}
protected doFetch ( ) : Promise < Response > {
throw new Error ( 'FixtureApiClient overrides all protocol paths; doFetch must be unreachable' )
@@ -525,10 +1096,22 @@ export class FixtureApiClient extends AbstractApiClient {
case 'session.list' : return this . api . sessions . list ( request )
case 'session.create' : return this . api . sessions . create ( request )
case 'session.history' : return this . api . sessions . history ( request )
case 'session.models' : return this . api . sessions . models ( request )
case 'session.selectModel' : return this . api . sessions . selectModel ( request )
case 'session.prompt' : return this . api . sessions . prompt ( request )
case 'session.cancel' : return this . api . sessions . cancel ( request )
case 'host.describe' : return this . api . host . describe ( request )
case 'goal.get ' : return this . api . goals . get ( request )
case 'host.pickDirectory ' : return this . api . host . pickDirectory ( request , new AbortController ( ) . signal )
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 )
case 'workspace.delete' : return this . api . workspace . delete ( request )
case 'workspace.insertSessionBefore' : return this . api . workspace . insertSessionBefore ( request )
case 'command.list' : return this . api . commands . list ( request )
// The in-memory execute never blocks, so a never-aborting signal is faithful here.
case 'command.execute' : return this . api . commands . execute ( request , new AbortController ( ) . signal )
case 'skill.list' : return this . api . skills . list ( request )
case 'goal.create' : return this . api . goals . create ( request )
case 'goal.edit' : return this . api . goals . edit ( request )
case 'goal.pause' : return this . api . goals . pause ( request )
@@ -579,3 +1162,16 @@ export class FixtureApiClient extends AbstractApiClient {
return this . api . respond ( message )
}
}
/** Browser query mapping; direct unit callers pass FixtureOptions explicitly. */
function fixtureOptionsFromLocation ( ) : FixtureOptions {
if ( typeof location === 'undefined' ) return { }
const query = new URLSearchParams ( location . search )
return {
empty : query.get ( 'fixture' ) === 'empty' ,
rejectPrompt : query.get ( 'fixturePrompt' ) === 'reject' ,
failWorkspaceAttach : query.get ( 'fixtureAttach' ) === 'fail' ,
dropSessionCreateResponse : query.get ( 'fixtureSessionCreate' ) === 'drop-response' ,
createFrameOrder : query.get ( 'fixtureFrames' ) === 'workspace-first' ? 'workspace-first' : 'session-first' ,
}
}