@@ -52,10 +52,16 @@ function makePi() {
5252 return { pi, tools, lifecycle, busHandlers } ;
5353}
5454
55- function ctx ( ) {
55+ function ctx ( setWidget = vi . fn ( ) ) {
5656 return {
5757 hasUI : false ,
58- ui : { setStatus : vi . fn ( ) , setWidget : vi . fn ( ) , notify : vi . fn ( ) } ,
58+ ui : {
59+ setStatus : vi . fn ( ) ,
60+ setWidget,
61+ notify : vi . fn ( ) ,
62+ onTerminalInput : vi . fn ( ( ) => vi . fn ( ) ) ,
63+ getEditorText : vi . fn ( ( ) => "" ) ,
64+ } ,
5965 cwd : process . cwd ( ) ,
6066 model : undefined ,
6167 modelRegistry : { find : vi . fn ( ) , getAvailable : vi . fn ( ( ) => [ ] ) } ,
@@ -146,6 +152,43 @@ describe("issue #142: RPC handlers + subagents:ready are gated on session_start"
146152 expect ( reply ! [ 1 ] . data . id ) . toBeTruthy ( ) ;
147153 } ) ;
148154
155+ it ( "shows live tool activity for an RPC-spawned background agent" , async ( ) => {
156+ const { pi, lifecycle, busHandlers } = makePi ( ) ;
157+ let widgetFactory : any ;
158+ const setWidget = vi . fn ( ( key : string , content : any ) => {
159+ if ( key === "agents" && content ) widgetFactory = content ;
160+ } ) ;
161+ const extensionCtx = ctx ( setWidget ) ;
162+ let onToolActivity : ( ( activity : { type : "start" | "end" ; toolName : string } ) => void ) | undefined ;
163+ vi . mocked ( runAgent ) . mockImplementation ( ( _ctx , _type , _prompt , options : any ) => {
164+ onToolActivity = options . onToolActivity ;
165+ options . onSessionCreated ?.( { subscribe : ( ) => vi . fn ( ) } ) ;
166+ return new Promise ( ( ) => { } ) as any ;
167+ } ) ;
168+ subagentsExtension ( pi ) ;
169+
170+ await lifecycle . get ( "session_start" ) ( { } , extensionCtx ) ;
171+ // TaskExecute runs inside a root tool call, so the extension already has
172+ // the UI context before pi-tasks sends its cross-extension spawn request.
173+ await lifecycle . get ( "tool_execution_start" ) ( { } , extensionCtx ) ;
174+ await busHandlers . get ( "subagents:rpc:spawn" ) ! ( {
175+ requestId : "req-activity" ,
176+ type : "general-purpose" ,
177+ prompt : "go" ,
178+ options : { description : "rpc activity test" , isBackground : true } ,
179+ } ) ;
180+ await vi . waitFor ( ( ) => expect ( onToolActivity ) . toBeTypeOf ( "function" ) ) ;
181+ onToolActivity ! ( { type : "start" , toolName : "bash" } ) ;
182+
183+ expect ( widgetFactory ) . toBeTypeOf ( "function" ) ;
184+ const lines = widgetFactory (
185+ { terminal : { columns : 120 } , requestRender : vi . fn ( ) } ,
186+ { fg : ( _color : string , text : string ) => text , bold : ( text : string ) => text } ,
187+ ) . render ( ) . join ( "\n" ) ;
188+ expect ( lines ) . toContain ( "running command…" ) ;
189+ expect ( lines ) . not . toContain ( "thinking…" ) ;
190+ } ) ;
191+
149192 it ( "is idempotent — a second session_start does not re-advertise or double-register" , async ( ) => {
150193 const { pi, lifecycle } = makePi ( ) ;
151194 subagentsExtension ( pi ) ;
0 commit comments