@@ -32,13 +32,15 @@ type MockClient = {
3232 brokerPid ?: number
3333 baseUrl ?: string
3434 agentNames : string [ ]
35+ agentRuntimes : Record < string , 'pty' | 'headless' >
3536}
3637
3738const mock = vi . hoisted ( ( ) => {
3839 function createMockClient ( agentNames : string [ ] = [ ] ) : MockClient {
39- const agentRuntimes = new Map < string , 'pty' | 'headless' > ( )
40+ const agentRuntimes = new Map < string , 'pty' | 'headless' > ( agentNames . map ( ( name ) => [ name , 'pty' ] ) )
4041 const client : MockClient = {
4142 agentNames : [ ...agentNames ] ,
43+ agentRuntimes : Object . fromEntries ( agentRuntimes ) as Record < string , 'pty' | 'headless' > ,
4244 getSession : vi . fn ( async ( ) => ( { } ) ) ,
4345 listAgents : vi . fn ( async ( ) => client . agentNames . map ( ( name ) => ( { name, runtime : agentRuntimes . get ( name ) ?? 'pty' , channels : [ ] } ) ) ) ,
4446 getInboundDeliveryMode : vi . fn ( async ( ) => 'passthrough' ) ,
@@ -545,6 +547,60 @@ describe('BrokerManager local + cloud coexistence', () => {
545547 await manager . shutdown ( )
546548 } )
547549
550+ it ( 'spawns OpenCode with headless runtime and skips PTY attach operations' , async ( ) => {
551+ const manager = new BrokerManager ( )
552+ const local = await startLocal ( manager , [ ] )
553+
554+ const spawned = await manager . spawnAgent ( PROJECT_ID , { name : 'opencode-1' , cli : 'opencode' } )
555+ const attached = await manager . attachTerminal ( PROJECT_ID , {
556+ name : spawned . name ,
557+ mode : 'passthrough' ,
558+ rows : 24 ,
559+ cols : 80
560+ } )
561+
562+ expect ( spawned ) . toEqual ( { name : 'opencode-1' , runtime : 'headless' } )
563+ expect ( local . spawnCli ) . toHaveBeenCalledWith ( expect . objectContaining ( {
564+ name : 'opencode-1' ,
565+ cli : expect . stringContaining ( 'opencode' ) ,
566+ transport : 'headless'
567+ } ) )
568+ expect ( local . spawnPty ) . not . toHaveBeenCalled ( )
569+ expect ( local . resizePty ) . not . toHaveBeenCalled ( )
570+ expect ( local . snapshot ) . not . toHaveBeenCalled ( )
571+ expect ( attached ) . toEqual ( {
572+ name : 'opencode-1' ,
573+ mode : 'auto_inject' ,
574+ previousMode : 'passthrough' ,
575+ pending : 0 ,
576+ runtime : 'headless'
577+ } )
578+
579+ await manager . shutdown ( )
580+ } )
581+
582+ it ( 'remembers headless runtime from discovered agents before attaching' , async ( ) => {
583+ const manager = new BrokerManager ( )
584+ const local = await startLocal ( manager , [ ] )
585+ local . listAgents . mockResolvedValue ( [
586+ { name : 'opencode-1' , runtime : 'headless' , channels : [ ] }
587+ ] )
588+
589+ const attached = await manager . attachTerminal ( PROJECT_ID , {
590+ name : 'opencode-1' ,
591+ mode : 'passthrough' ,
592+ rows : 24 ,
593+ cols : 80
594+ } )
595+
596+ expect ( local . setInboundDeliveryMode ) . toHaveBeenCalledWith ( 'opencode-1' , 'auto_inject' )
597+ expect ( local . resizePty ) . not . toHaveBeenCalled ( )
598+ expect ( local . snapshot ) . not . toHaveBeenCalled ( )
599+ expect ( attached . runtime ) . toBe ( 'headless' )
600+
601+ await manager . shutdown ( )
602+ } )
603+
548604 it ( 'coalesces concurrent duplicate spawn requests' , async ( ) => {
549605 const manager = new BrokerManager ( )
550606 const local = await startLocal ( manager , [ ] )
0 commit comments