2026-09-12 09:36:36 +00:00
/ * * R u n a g a i n s t a d i s p o s a b l e A P I w i t h S A N D B O X _ P R O V I D E R = f a k e a n d a f r e s h D B .
* LAZYBOY _TEST _API = http : //127.0.0.1:3118 node --test tests/task-experience.test.mjs
* The mock provider is reachable from the API at host . docker . internal : 3119.
* Never point this test at a workspace containing real user data .
* /
import { test } from 'node:test' ;
import assert from 'node:assert/strict' ;
import http from 'node:http' ;
import { randomUUID } from 'node:crypto' ;
const base = process . env . LAZYBOY _TEST _API ;
test ( 'one assignment: verified completion, recovery, and same-run takeover resume' , { skip : ! base , timeout : 60000 } , async ( ) => {
assert . equal ( new URL ( base ) . port , '3118' , 'use the isolated test API' ) ;
2026-09-12 10:49:47 +00:00
let cookie = '' ; const calls = { } ; const histories = { } ; let handoffs = 0 ; let finalRecall = 0 ; let releasePriority ; const priorityGate = new Promise ( resolve => { releasePriority = resolve } ) ;
2026-09-12 09:36:36 +00:00
const report = ( state , summary , remaining = [ ] , extra = { } ) => ( { state , summary , completed : [ 'Compared the two supplied source excerpts' ] , remaining , verification : 'Checked both supplied excerpts and the requested comparison' , artifacts : [ ] , ... extra } ) ;
const fixture = http . createServer ( async ( req , res ) => {
let body = '' ; for await ( const chunk of req ) body += chunk ;
const input = JSON . parse ( body || '{}' ) ; const serialized = JSON . stringify ( input . messages || [ ] ) ;
2026-09-12 10:49:47 +00:00
const scenario = [ 'UX_RESEARCH' , 'UX_RECOVERY' , 'UX_TAKEOVER' , 'UX_CONTEXT' , 'UX_PRIORITY' ] . find ( tag => serialized . includes ( tag ) ) ;
if ( serialized . includes ( 'previous_handoff' ) ) {
handoffs ++ ;
const summary = JSON . stringify ( { objectives : 'Newest task first' , constraints : 'Never publish; palette violet' , decisions : 'Older unfinished work is parked' , completed : 'Prior local comparisons delivered' , remaining : 'Handle newest assignment only' , references : 'Read message 1 for exact wording' } ) ;
res . writeHead ( 200 , { 'content-type' : 'application/json' } ) ; res . end ( JSON . stringify ( { id : randomUUID ( ) , object : 'chat.completion' , created : 1 , model : input . model , choices : [ { index : 0 , message : { role : 'assistant' , content : handoffs === 1 ? 'invalid handoff' : summary } , finish _reason : 'stop' } ] , usage : { prompt _tokens : 10 , completion _tokens : 10 , total _tokens : 20 } } ) ) ; return ;
}
2026-09-12 09:36:36 +00:00
if ( ! scenario ) { res . writeHead ( 400 ) ; res . end ( 'unknown fixture' ) ; return ; }
const index = calls [ scenario ] || 0 ; calls [ scenario ] = index + 1 ; ( histories [ scenario ] ? ? = [ ] ) . push ( serialized ) ;
let name , args , text = 'Finished.' ;
if ( scenario === 'UX_RESEARCH' ) {
if ( index === 0 ) text = 'I have only planned it.\n[GOAL_COMPLETE]' ;
if ( index === 1 ) { name = 'report_task' ; args = report ( 'progress' , 'I checked the first excerpt.' , [ 'Compare the second excerpt' ] ) ; }
if ( index === 2 ) { name = 'report_task' ; args = report ( 'complete' , 'A and B both support delegation; B also requires verification.' ) ; }
} else if ( scenario === 'UX_RECOVERY' ) {
if ( index === 0 ) { name = 'read_file' ; args = { path : 'missing-source.txt' } ; }
if ( index === 1 ) { name = 'report_task' ; args = report ( 'recovering' , 'The file is missing. I will use the source excerpts you supplied.' , [ 'Compare the excerpts' ] , { attempts : [ 'read_file returned not found; switching to supplied source text' ] } ) ; }
if ( index === 2 ) { name = 'report_task' ; args = report ( 'complete' , 'Completed the comparison using the supplied sources.' ) ; }
2026-09-12 10:49:47 +00:00
} else if ( scenario === 'UX_CONTEXT' ) {
if ( serialized . includes ( 'UX_CONTEXT_FINAL' ) && finalRecall ++ === 0 ) { name = 'read_conversation' ; args = { query : 'UX_CONTEXT_SEED' , limit : 1 } ; }
else { name = 'report_task' ; args = report ( 'complete' , 'Completed the latest comparison; original constraint retained.' ) ; }
} else if ( scenario === 'UX_PRIORITY' ) {
if ( index === 0 ) await priorityGate ;
name = 'report_task' ; args = report ( 'complete' , index === 0 ? 'Old comparison done.' : 'Newest comparison delivered; older work parked.' ) ;
2026-09-12 09:36:36 +00:00
} else {
2026-09-12 10:49:47 +00:00
if ( index === 0 ) { name = 'request_takeover' ; args = { intervention : 'verification' , site : 'Local test fixture' , reason : 'Complete the human verification step.' , why : 'I will finish the original comparison.' } ; }
2026-09-12 09:36:36 +00:00
if ( index === 1 ) { name = 'report_task' ; args = report ( 'complete' , 'Resumed and completed the original comparison.' ) ; }
}
const id = randomUUID ( ) ; const choice = name ? { role : 'assistant' , tool _calls : [ { id , type : 'function' , function : { name , arguments : JSON . stringify ( args ) } } ] } : { role : 'assistant' , content : text } ;
if ( input . stream ) {
res . writeHead ( 200 , { 'content-type' : 'text/event-stream' } ) ;
const chunk = delta => ( { id , object : 'chat.completion.chunk' , created : 1 , model : input . model , choices : [ { index : 0 , delta , finish _reason : null } ] } ) ;
res . write ( ` data: ${ JSON . stringify ( chunk ( name ? { role : 'assistant' , tool _calls : [ { index : 0 , ... choice . tool _calls [ 0 ] } ]}:{role:'assistant',content:text}))} \n \n ` ) ;
res . write ( ` data: ${ JSON . stringify ( { id , object : 'chat.completion.chunk' , created : 1 , model : input . model , choices : [ { index : 0 , delta : { } ,finish_reason:name?'tool_calls':'stop'}],usage:{prompt_tokens:10,completion_tokens:10,total_tokens:20}})} \n \n data: [DONE] \n \n ` ) ; res . end ( ) ;
} else { res . writeHead ( 200 , { 'content-type' : 'application/json' } ) ; res . end ( JSON . stringify ( { id , object : 'chat.completion' , created : 1 , model : input . model , choices : [ { index : 0 , message : choice , finish _reason : name ? 'tool_calls' : 'stop' } ] , usage : { prompt _tokens : 10 , completion _tokens : 10 , total _tokens : 20 } } ) ) ; }
} ) ;
await new Promise ( resolve => fixture . listen ( 3119 , '0.0.0.0' , resolve ) ) ;
async function api ( path , body , method = body ? 'POST' : 'GET' ) {
const r = await fetch ( base + path , { method , headers : { 'content-type' : 'application/json' , cookie } , body : body ? JSON . stringify ( body ) : undefined } ) ;
if ( r . headers . get ( 'set-cookie' ) ) cookie = r . headers . get ( 'set-cookie' ) . split ( ';' ) [ 0 ] ;
const raw = await r . text ( ) ; assert . ok ( r . ok , ` ${ path } : ${ r . status } ${ raw } ` ) ; return raw ? JSON . parse ( raw ) : null ;
}
async function until ( fn , label ) { for ( let i = 0 ; i < 150 ; i ++ ) { const found = await fn ( ) ; if ( found ) return found ; await new Promise ( r => setTimeout ( r , 150 ) ) ; } throw Error ( ` Timed out: ${ label } ` ) ; }
try {
await api ( '/api/auth/register' , { username : 'ux' + randomUUID ( ) . replaceAll ( '-' , '' ) . slice ( 0 , 12 ) , password : 'FixtureOnly123!' } ) ;
await api ( '/api/workspace/settings' , { provider : 'openai-compatible' , modelId : 'fixture-vision' , baseUrl : 'http://host.docker.internal:3119/v1' , apiKey : 'fixture-only' } , 'PATCH' ) ;
const bot = await api ( '/api/bots' , { name : 'UX fixture' , computerMode : 'team' , memoryEnabled : false } ) ;
for ( const scenario of [ 'UX_RESEARCH' , 'UX_RECOVERY' , 'UX_TAKEOVER' ] ) {
const session = await api ( ` /api/bots/ ${ bot . id } /sessions ` , { title : scenario } ) ;
await api ( ` /api/sessions/ ${ session . id } /messages ` , { text : ` ${ scenario } : Compare these sources and deliver a concise report. Source A: assign the outcome once. Source B: assign the outcome once and verify the result. Use supplied excerpts if a file is missing. ` , clientNonce : randomUUID ( ) } ) ;
let run ;
if ( scenario === 'UX_TAKEOVER' ) {
run = await until ( async ( ) => { const [ r ] = await api ( ` /api/sessions/ ${ session . id } /task ` ) ; return r ? . status === 'waiting_takeover' ? r : null ; } , 'takeover' ) ;
const messages = await api ( ` /api/sessions/ ${ session . id } /messages ` ) ;
assert . equal ( messages . flatMap ( m => m . blocks || [ ] ) . find ( b => b . kind === 'login' ) ? . intervention , 'verification' ) ;
await api ( ` /api/computer/ ${ bot . id } /release ` , { } ) ;
}
const completed = await until ( async ( ) => { const [ r ] = await api ( ` /api/sessions/ ${ session . id } /task ` ) ; if ( r ? . status === 'failed' ) throw Error ( ` run failed: ${ scenario } ` ) ; return r ? . status === 'completed' ? r : null ; } , scenario ) ;
assert . equal ( completed . report . state , 'complete' ) ; assert . deepEqual ( completed . report . remaining , [ ] ) ;
if ( run ) assert . equal ( completed . runId , run . runId , 'human releases control; original run resumes' ) ;
const messages = await api ( ` /api/sessions/ ${ session . id } /messages ` ) ;
assert . equal ( messages . filter ( m => m . role === 'user' ) . length , 1 , 'no repeated instruction' ) ;
assert . match ( messages . at ( - 1 ) . body , /comparison|delegation/ ) ;
if ( scenario === 'UX_RESEARCH' ) assert . equal ( calls [ scenario ] , 3 , 'reject bare completion, then deliver without an extra model call' ) ;
if ( scenario === 'UX_RECOVERY' ) assert . equal ( messages . filter ( m => m . blocks ? . some ( b => b . kind === 'progress' ) ) . length , 1 ) ;
if ( scenario === 'UX_TAKEOVER' ) assert . match ( histories [ scenario ] [ 1 ] , /CURRENT screen|current screen/ ) ;
console . log ( ` ${ scenario } : complete, one user assignment, ${ run ? 'one verification step' : 'zero intervention' } ` ) ;
}
2026-09-12 10:49:47 +00:00
const contextSession = await api ( ` /api/bots/ ${ bot . id } /sessions ` , { title : 'UX_CONTEXT' } ) ;
for ( let i = 0 ; i < 18 ; i ++ ) {
await api ( ` /api/sessions/ ${ contextSession . id } /messages ` , { text : i === 0 ? 'UX_CONTEXT_SEED: Compare A and B locally. Constraint: Never publish; palette violet.' : ` UX_CONTEXT: Complete local comparison number ${ i } , supplied A=one B=two. ` , clientNonce : randomUUID ( ) } ) ;
await until ( async ( ) => { const [ r ] = await api ( ` /api/sessions/ ${ contextSession . id } /task ` ) ; return r ? . status === 'completed' ; } , 'long conversation' ) ;
}
await until ( async ( ) => { const sessions = await api ( ` /api/bots/ ${ bot . id } /sessions ` ) ; return sessions . find ( s => s . id === contextSession . id ) ? . historySummarySeq > 0 ; } , 'background handoff persisted' ) ;
assert . ok ( handoffs >= 2 , 'failed handoff is retried automatically on a later turn' ) ;
await api ( ` /api/sessions/ ${ contextSession . id } /messages ` , { text : 'UX_CONTEXT_FINAL: Retrieve the exact first constraint and deliver the newest comparison.' , clientNonce : randomUUID ( ) } ) ;
await until ( async ( ) => { const [ r ] = await api ( ` /api/sessions/ ${ contextSession . id } /task ` ) ; return r ? . status === 'completed' ; } , 'archive recall' ) ;
const transcript = await api ( ` /api/sessions/ ${ contextSession . id } /messages ` ) ;
assert . equal ( transcript . filter ( m => m . role === 'user' ) . length , 19 , 'original messages stay in the same session' ) ;
assert . match ( transcript [ 0 ] . body , /UX_CONTEXT_SEED/ ) ;
assert . match ( histories . UX _CONTEXT . at ( - 1 ) , /Never publish; palette violet/ ) ;
assert . match ( histories . UX _CONTEXT . at ( - 1 ) , /UX_CONTEXT_SEED/ , 'assistant retrieves original text outside recent window' ) ;
const sessionList = await api ( ` /api/bots/ ${ bot . id } /sessions ` ) ;
const compacted = sessionList . find ( s => s . id === contextSession . id ) ;
assert . ok ( compacted . historySummarySeq > 0 ) ;
console . log ( 'UX_CONTEXT: automatic handoff, original transcript retained, exact old message recalled' ) ;
const prioritySession = await api ( ` /api/bots/ ${ bot . id } /sessions ` , { title : 'UX_PRIORITY' } ) ;
const original = await api ( ` /api/sessions/ ${ prioritySession . id } /messages ` , { text : 'UX_PRIORITY: Complete old comparison A.' , clientNonce : randomUUID ( ) } ) ;
await until ( async ( ) => calls . UX _PRIORITY > 0 , 'old model request' ) ;
const newest = await api ( ` /api/sessions/ ${ prioritySession . id } /messages ` , { text : 'UX_PRIORITY_NEW: Prioritize new comparison B; park A.' , clientNonce : randomUUID ( ) } ) ;
releasePriority ( ) ;
assert . equal ( newest . runId , original . runId , 'newest instruction steers without requiring session changes' ) ;
await until ( async ( ) => { const [ r ] = await api ( ` /api/sessions/ ${ prioritySession . id } /task ` ) ; return r ? . status === 'completed' ; } , 'new task priority' ) ;
const priorityMessages = await api ( ` /api/sessions/ ${ prioritySession . id } /messages ` ) ;
assert . match ( priorityMessages . at ( - 1 ) . body , /Newest comparison/ ) ;
assert . match ( histories . UX _PRIORITY . at ( - 1 ) , /newest user message has highest task priority/i ) ;
assert . match ( histories . UX _PRIORITY . at ( - 1 ) , /UX_PRIORITY_NEW/ ) ;
assert . match ( histories . UX _PRIORITY . at ( - 1 ) , /Not executed: a newer user instruction arrived/ , 'old planned tool never executes after a new assignment arrives' ) ;
console . log ( 'UX_PRIORITY: newest instruction wins before old completion can end the run' ) ;
2026-09-12 09:36:36 +00:00
} finally { fixture . closeAllConnections ( ) ; await new Promise ( resolve => fixture . close ( resolve ) ) ; }
} ) ;