44 lines
4.3 KiB
TypeScript
44 lines
4.3 KiB
TypeScript
import { spawn } from 'node:child_process';
|
|
import { mkdtemp,mkdir,rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { OpenCode } from '@opencode/client';
|
|
// Opt in: RUN_HOST_QUESTION_NATIVE_SMOKE=1 bun tests/integration/hostQuestionNativeSmoke.ts
|
|
// Uses only owned temporary roots and resume:false; never calls a provider.
|
|
if (process.env.RUN_HOST_QUESTION_NATIVE_SMOKE !== "1") {
|
|
console.log("Skipped native host-question smoke; set RUN_HOST_QUESTION_NATIVE_SMOKE=1 to run.");
|
|
process.exit(0);
|
|
}
|
|
const root=await mkdtemp(join(tmpdir(),'tjwater-native-question-'));
|
|
await mkdir(join(root,'home')); await mkdir(join(root,'workspace'));
|
|
let child:any;
|
|
async function start(){
|
|
child=spawn(process.env.OPENCODE_COMMAND ?? 'opencode',['serve','--hostname=127.0.0.1','--port=0'],{cwd:root,env:{PATH:process.env.PATH,HOME:join(root,'home'),XDG_CONFIG_HOME:join(root,'config'),XDG_DATA_HOME:join(root,'data'),XDG_CACHE_HOME:join(root,'cache'),OPENCODE_DB:join(root,'native.sqlite'),OPENCODE_CONFIG_CONTENT:JSON.stringify({})},stdio:['ignore','pipe','pipe']});
|
|
const endpoint=await new Promise<any>((resolve,reject)=>{let output='';const timer=setTimeout(()=>reject(new Error('startup timeout')),20000);child.on('exit',()=>reject(new Error('startup exit')));child.stderr.resume();child.stdout.on('data',(chunk:any)=>{output+=chunk;const url=/server listening on (https?:\/\/\S+)/.exec(output)?.[1];const password=/server password (\S+)/.exec(output)?.[1];if(url&&password){clearTimeout(timer);resolve({url,password});}});});
|
|
return OpenCode.make({baseUrl:endpoint.url,headers:{authorization:`Basic ${Buffer.from(`opencode:${endpoint.password}`).toString('base64')}`}});
|
|
}
|
|
async function stop(){if(!child)return; const closing=new Promise<void>(r=>child.once('close',()=>r()));child.kill('SIGTERM');await closing;child=undefined;}
|
|
try{
|
|
let client=await start();
|
|
const session=await client.session.create({title:'No-inference host question smoke',location:{directory:join(root,'workspace')}});
|
|
const input={sessionID:session.id,id:'msg_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',text:'Durable fixed-ID user answer',resume:false};
|
|
await client.session.prompt(input);
|
|
let duplicate='accepted';try{await client.session.prompt(input);}catch(e:any){duplicate=e?._tag??e?.name??'rejected';}
|
|
const queued=await client.session.inbox.list({sessionID:session.id});
|
|
if(queued.filter((x:any)=>x.id===input.id).length!==1)throw new Error('fixed input duplicated');
|
|
if((await client.session.active())[session.id])throw new Error('resume:false started inference');
|
|
const form=await client.session.form.create({sessionID:session.id,title:'Smoke question',fields:[{key:'q',type:'string',required:true}]});
|
|
const before=await client.session.form.get({sessionID:session.id,formID:form.id});
|
|
if(before.state.status !== 'pending') throw new Error('new form was not pending');
|
|
await client.session.form.reply({sessionID:session.id,formID:form.id,answer:{q:'answer'}});
|
|
let terminal='unknown';try{terminal=(await client.session.form.get({sessionID:session.id,formID:form.id})).state.status;}catch(e:any){terminal=e?._tag;}
|
|
const cancelled=await client.session.form.create({sessionID:session.id,title:'Cancel smoke',fields:[{key:'q',type:'string'}]});
|
|
await client.session.form.cancel({sessionID:session.id,formID:cancelled.id});
|
|
let cancelledState='unknown';try{cancelledState=(await client.session.form.get({sessionID:session.id,formID:cancelled.id})).state.status;}catch(e:any){cancelledState=e?._tag;}
|
|
await stop();client=await start();
|
|
const recovered=await client.session.inbox.list({sessionID:session.id});
|
|
if(recovered.filter((x:any)=>x.id===input.id).length!==1)throw new Error('fixed input lost after restart');
|
|
let restored='unknown';try{restored=(await client.session.form.get({sessionID:session.id,formID:form.id})).state.status;}catch(e:any){restored=e?._tag;}
|
|
console.log(JSON.stringify({native:'current isolated executable',fixed_id_unique:true,duplicate_result:duplicate,restart_durable:true,form_pending:before.state.status,form_after_reply:terminal,form_after_cancel:cancelledState,form_after_restart:restored,inference_started:false}));
|
|
}catch(e:any){console.log(JSON.stringify({error:e?.message,tag:e?._tag,name:e?.name}));process.exitCode=1;}finally{await stop();await rm(root,{recursive:true,force:true});}
|