2025-09-30 17:00:54 -04:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2026-03-02 21:04:31 +00:00
import {
describe ,
it ,
expect ,
vi ,
beforeEach ,
afterEach ,
type Mocked ,
} from 'vitest' ;
2026-03-04 05:42:59 +05:30
import {
AgentTerminateMode ,
type LocalAgentDefinition ,
type SubagentActivityEvent ,
type AgentInputs ,
type SubagentProgress ,
2026-03-18 21:09:37 -04:00
SubagentActivityErrorType ,
SUBAGENT_REJECTED_ERROR_PREFIX ,
2026-02-21 13:33:25 -05:00
} from './types.js' ;
2025-12-17 12:06:38 -05:00
import { LocalSubagentInvocation } from './local-invocation.js' ;
import { LocalAgentExecutor } from './local-executor.js' ;
2025-09-30 17:00:54 -04:00
import { makeFakeConfig } from '../test-utils/config.js' ;
import type { Config } from '../config/config.js' ;
2025-10-01 16:54:00 -04:00
import type { MessageBus } from '../confirmation-bus/message-bus.js' ;
2025-10-03 13:21:08 -04:00
import { type z } from 'zod' ;
2026-01-04 17:11:43 -05:00
import { createMockMessageBus } from '../test-utils/mock-message-bus.js' ;
2025-09-30 17:00:54 -04:00
2025-12-17 12:06:38 -05:00
vi . mock ( './local-executor.js' ) ;
2025-09-30 17:00:54 -04:00
2025-12-17 12:06:38 -05:00
const MockLocalAgentExecutor = vi . mocked ( LocalAgentExecutor ) ;
2025-09-30 17:00:54 -04:00
let mockConfig : Config ;
2025-12-17 12:06:38 -05:00
const testDefinition : LocalAgentDefinition < z.ZodUnknown > = {
kind : 'local' ,
2025-09-30 17:00:54 -04:00
name : 'MockAgent' ,
2026-03-02 21:04:31 +00:00
displayName : 'Mock Agent' ,
2025-09-30 17:00:54 -04:00
description : 'A mock agent.' ,
inputConfig : {
2026-01-21 16:56:01 -08:00
inputSchema : {
type : 'object' ,
properties : {
task : { type : 'string' , description : 'task' } ,
priority : { type : 'number' , description : 'prio' } ,
} ,
required : [ 'task' ] ,
2025-09-30 17:00:54 -04:00
} ,
} ,
2026-01-13 14:31:34 -08:00
modelConfig : {
model : 'test' ,
generateContentConfig : {
temperature : 0 ,
topP : 1 ,
} ,
} ,
runConfig : { maxTimeMinutes : 1 } ,
2025-09-30 17:00:54 -04:00
promptConfig : { systemPrompt : 'test' } ,
} ;
2025-12-17 12:06:38 -05:00
describe ( 'LocalSubagentInvocation' , ( ) = > {
let mockExecutorInstance : Mocked < LocalAgentExecutor < z.ZodUnknown > > ;
2026-01-04 17:11:43 -05:00
let mockMessageBus : MessageBus ;
2025-09-30 17:00:54 -04:00
beforeEach ( ( ) = > {
vi . clearAllMocks ( ) ;
mockConfig = makeFakeConfig ( ) ;
2026-03-10 18:12:59 -07:00
// .config is already set correctly by the getter on the instance.
Object . defineProperty ( mockConfig , 'promptId' , {
get : ( ) = > 'test-prompt-id' ,
configurable : true ,
} ) ;
2026-01-04 17:11:43 -05:00
mockMessageBus = createMockMessageBus ( ) ;
2025-09-30 17:00:54 -04:00
mockExecutorInstance = {
run : vi.fn ( ) ,
definition : testDefinition ,
2026-04-10 12:47:25 -04:00
agentId : 'test-agent-id' ,
2025-12-17 12:06:38 -05:00
} as unknown as Mocked < LocalAgentExecutor < z.ZodUnknown > > ;
2025-09-30 17:00:54 -04:00
2025-12-17 12:06:38 -05:00
MockLocalAgentExecutor . create . mockResolvedValue (
mockExecutorInstance as unknown as LocalAgentExecutor < z.ZodTypeAny > ,
2025-10-03 13:21:08 -04:00
) ;
2025-09-30 17:00:54 -04:00
} ) ;
2026-03-02 21:04:31 +00:00
afterEach ( ( ) = > {
vi . restoreAllMocks ( ) ;
} ) ;
2025-10-01 16:54:00 -04:00
it ( 'should pass the messageBus to the parent constructor' , ( ) = > {
const params = { task : 'Analyze data' } ;
2025-12-17 12:06:38 -05:00
const invocation = new LocalSubagentInvocation (
2025-10-01 16:54:00 -04:00
testDefinition ,
mockConfig ,
2025-12-17 12:06:38 -05:00
params ,
2025-10-01 16:54:00 -04:00
mockMessageBus ,
) ;
// Access the protected messageBus property by casting to any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect ( ( invocation as any ) . messageBus ) . toBe ( mockMessageBus ) ;
} ) ;
2025-09-30 17:00:54 -04:00
describe ( 'getDescription' , ( ) = > {
it ( 'should format the description with inputs' , ( ) = > {
const params = { task : 'Analyze data' , priority : 5 } ;
2025-12-17 12:06:38 -05:00
const invocation = new LocalSubagentInvocation (
2025-09-30 17:00:54 -04:00
testDefinition ,
mockConfig ,
2025-12-17 12:06:38 -05:00
params ,
2026-01-04 17:11:43 -05:00
mockMessageBus ,
2025-09-30 17:00:54 -04:00
) ;
const description = invocation . getDescription ( ) ;
expect ( description ) . toBe (
"Running subagent 'MockAgent' with inputs: { task: Analyze data, priority: 5 }" ,
) ;
} ) ;
it ( 'should truncate long input values' , ( ) = > {
const longTask = 'A' . repeat ( 100 ) ;
const params = { task : longTask } ;
2025-12-17 12:06:38 -05:00
const invocation = new LocalSubagentInvocation (
2025-09-30 17:00:54 -04:00
testDefinition ,
mockConfig ,
2025-12-17 12:06:38 -05:00
params ,
2026-01-04 17:11:43 -05:00
mockMessageBus ,
2025-09-30 17:00:54 -04:00
) ;
const description = invocation . getDescription ( ) ;
// Default INPUT_PREVIEW_MAX_LENGTH is 50
expect ( description ) . toBe (
` Running subagent 'MockAgent' with inputs: { task: ${ 'A' . repeat ( 50 ) } } ` ,
) ;
} ) ;
it ( 'should truncate the overall description if it exceeds the limit' , ( ) = > {
// Create a definition and inputs that result in a very long description
2025-12-17 12:06:38 -05:00
const longNameDef : LocalAgentDefinition = {
2025-09-30 17:00:54 -04:00
. . . testDefinition ,
name : 'VeryLongAgentNameThatTakesUpSpace' ,
} ;
const params : AgentInputs = { } ;
for ( let i = 0 ; i < 20 ; i ++ ) {
params [ ` input ${ i } ` ] = ` value ${ i } ` ;
}
2025-12-17 12:06:38 -05:00
const invocation = new LocalSubagentInvocation (
2025-09-30 17:00:54 -04:00
longNameDef ,
mockConfig ,
2025-12-17 12:06:38 -05:00
params ,
2026-01-04 17:11:43 -05:00
mockMessageBus ,
2025-09-30 17:00:54 -04:00
) ;
const description = invocation . getDescription ( ) ;
// Default DESCRIPTION_MAX_LENGTH is 200
expect ( description . length ) . toBe ( 200 ) ;
expect (
description . startsWith (
"Running subagent 'VeryLongAgentNameThatTakesUpSpace'" ,
) ,
) . toBe ( true ) ;
} ) ;
} ) ;
describe ( 'execute' , ( ) = > {
let signal : AbortSignal ;
let updateOutput : ReturnType < typeof vi.fn > ;
const params = { task : 'Execute task' } ;
2025-12-17 12:06:38 -05:00
let invocation : LocalSubagentInvocation ;
2025-09-30 17:00:54 -04:00
beforeEach ( ( ) = > {
signal = new AbortController ( ) . signal ;
updateOutput = vi . fn ( ) ;
2025-12-17 12:06:38 -05:00
invocation = new LocalSubagentInvocation (
2025-10-03 13:21:08 -04:00
testDefinition ,
mockConfig ,
2025-12-17 12:06:38 -05:00
params ,
2026-01-04 17:11:43 -05:00
mockMessageBus ,
2025-10-03 13:21:08 -04:00
) ;
2025-09-30 17:00:54 -04:00
} ) ;
it ( 'should initialize and run the executor successfully' , async ( ) = > {
const mockOutput = {
result : 'Analysis complete.' ,
terminate_reason : AgentTerminateMode.GOAL ,
} ;
mockExecutorInstance . run . mockResolvedValue ( mockOutput ) ;
2026-04-10 10:11:17 -07:00
const result = await invocation . execute ( {
abortSignal : signal ,
updateOutput ,
} ) ;
2025-09-30 17:00:54 -04:00
2025-12-17 12:06:38 -05:00
expect ( MockLocalAgentExecutor . create ) . toHaveBeenCalledWith (
2025-09-30 17:00:54 -04:00
testDefinition ,
mockConfig ,
expect . any ( Function ) ,
) ;
2026-03-02 21:04:31 +00:00
expect ( updateOutput ) . toHaveBeenCalledWith (
expect . objectContaining ( {
isSubagentProgress : true ,
agentName : 'MockAgent' ,
} ) ,
) ;
2025-09-30 17:00:54 -04:00
expect ( mockExecutorInstance . run ) . toHaveBeenCalledWith ( params , signal ) ;
expect ( result . llmContent ) . toEqual ( [
{
text : expect.stringContaining (
"Subagent 'MockAgent' finished.\nTermination Reason: GOAL\nResult:\nAnalysis complete." ,
) ,
} ,
] ) ;
2026-03-17 23:11:20 -04:00
const display = result . returnDisplay as SubagentProgress ;
expect ( display . isSubagentProgress ) . toBe ( true ) ;
expect ( display . state ) . toBe ( 'completed' ) ;
expect ( display . result ) . toBe ( 'Analysis complete.' ) ;
expect ( display . terminateReason ) . toBe ( AgentTerminateMode . GOAL ) ;
2026-03-11 17:11:07 -04:00
} ) ;
it ( 'should show detailed UI for non-goal terminations (e.g., TIMEOUT)' , async ( ) = > {
const mockOutput = {
result : 'Partial progress...' ,
terminate_reason : AgentTerminateMode.TIMEOUT ,
} ;
mockExecutorInstance . run . mockResolvedValue ( mockOutput ) ;
2026-04-10 10:11:17 -07:00
const result = await invocation . execute ( {
abortSignal : signal ,
updateOutput ,
} ) ;
2026-03-11 17:11:07 -04:00
2026-03-17 23:11:20 -04:00
const display = result . returnDisplay as SubagentProgress ;
expect ( display . isSubagentProgress ) . toBe ( true ) ;
expect ( display . state ) . toBe ( 'completed' ) ;
expect ( display . result ) . toBe ( 'Partial progress...' ) ;
expect ( display . terminateReason ) . toBe ( AgentTerminateMode . TIMEOUT ) ;
2025-09-30 17:00:54 -04:00
} ) ;
2026-03-18 19:20:31 +00:00
it ( 'should stream THOUGHT_CHUNK activities from the executor, replacing the last running thought' , async ( ) = > {
2025-09-30 17:00:54 -04:00
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
2025-12-17 12:06:38 -05:00
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
2025-09-30 17:00:54 -04:00
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'THOUGHT_CHUNK' ,
data : { text : 'Analyzing...' } ,
} as SubagentActivityEvent ) ;
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'THOUGHT_CHUNK' ,
2026-03-18 19:20:31 +00:00
data : { text : 'Thinking about next steps.' } ,
2025-09-30 17:00:54 -04:00
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2025-09-30 17:00:54 -04:00
2026-03-17 23:11:20 -04:00
expect ( updateOutput ) . toHaveBeenCalledTimes ( 4 ) ; // Initial + 2 updates + Final completion
const lastCall = updateOutput . mock . calls [ 3 ] [ 0 ] as SubagentProgress ;
2026-03-02 21:04:31 +00:00
expect ( lastCall . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'thought' ,
2026-03-18 19:20:31 +00:00
content : 'Thinking about next steps.' ,
} ) ,
) ;
expect ( lastCall . recentActivity ) . not . toContainEqual (
expect . objectContaining ( {
type : 'thought' ,
content : 'Analyzing...' ,
2026-03-02 21:04:31 +00:00
} ) ,
) ;
2025-09-30 17:00:54 -04:00
} ) ;
2026-03-20 15:19:18 -04:00
it ( 'should overwrite the thought content with new THOUGHT_CHUNK activity' , async ( ) = > {
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'THOUGHT_CHUNK' ,
data : { text : 'I am thinking.' } ,
} as SubagentActivityEvent ) ;
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'THOUGHT_CHUNK' ,
data : { text : 'Now I will act.' } ,
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2026-03-20 15:19:18 -04:00
const calls = updateOutput . mock . calls ;
const lastCall = calls [ calls . length - 1 ] [ 0 ] as SubagentProgress ;
expect ( lastCall . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'thought' ,
content : 'Now I will act.' ,
} ) ,
) ;
} ) ;
2026-03-02 21:04:31 +00:00
it ( 'should stream other activities (e.g., TOOL_CALL_START, ERROR)' , async ( ) = > {
2025-09-30 17:00:54 -04:00
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
2025-12-17 12:06:38 -05:00
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
2025-09-30 17:00:54 -04:00
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'TOOL_CALL_START' ,
2026-03-02 21:04:31 +00:00
data : { name : 'ls' , args : { } } ,
2025-09-30 17:00:54 -04:00
} as SubagentActivityEvent ) ;
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'ERROR' ,
data : { error : 'Failed' } ,
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2025-09-30 17:00:54 -04:00
2026-03-17 23:11:20 -04:00
expect ( updateOutput ) . toHaveBeenCalledTimes ( 4 ) ; // Initial + 2 updates + Final completion
const lastCall = updateOutput . mock . calls [ 3 ] [ 0 ] as SubagentProgress ;
2026-03-02 21:04:31 +00:00
expect ( lastCall . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'thought' ,
content : 'Error: Failed' ,
status : 'error' ,
} ) ,
) ;
2025-09-30 17:00:54 -04:00
} ) ;
2026-03-23 21:56:00 -04:00
it ( 'should mark tool call as error when TOOL_CALL_END contains isError: true' , async ( ) = > {
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'TOOL_CALL_START' ,
data : { name : 'ls' , args : { } , callId : 'call1' } ,
} as SubagentActivityEvent ) ;
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'TOOL_CALL_END' ,
data : { name : 'ls' , id : 'call1' , data : { isError : true } } ,
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2026-03-23 21:56:00 -04:00
expect ( updateOutput ) . toHaveBeenCalled ( ) ;
const lastCall = updateOutput . mock . calls [
updateOutput . mock . calls . length - 1
] [ 0 ] as SubagentProgress ;
expect ( lastCall . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'tool_call' ,
content : 'ls' ,
status : 'error' ,
} ) ,
) ;
} ) ;
2026-03-18 21:09:37 -04:00
it ( 'should reflect tool rejections in the activity stream as cancelled but not abort the agent' , async ( ) = > {
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'TOOL_CALL_START' ,
data : { name : 'ls' , args : { } , callId : 'call1' } ,
} as SubagentActivityEvent ) ;
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'ERROR' ,
data : {
name : 'ls' ,
callId : 'call1' ,
error : ` ${ SUBAGENT_REJECTED_ERROR_PREFIX } Please acknowledge this, rethink your strategy, and try a different approach. If you cannot proceed without the rejected operation, summarize the issue and use \` complete_task \` to report your findings and the blocker. ` ,
errorType : SubagentActivityErrorType.REJECTED ,
} ,
} as SubagentActivityEvent ) ;
}
return {
result : 'Rethinking...' ,
terminate_reason : AgentTerminateMode.GOAL ,
} ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2026-03-18 21:09:37 -04:00
expect ( updateOutput ) . toHaveBeenCalledTimes ( 4 ) ;
const lastCall = updateOutput . mock . calls [ 3 ] [ 0 ] as SubagentProgress ;
expect ( lastCall . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'tool_call' ,
content : 'ls' ,
status : 'cancelled' ,
} ) ,
) ;
} ) ;
2025-09-30 17:00:54 -04:00
it ( 'should run successfully without an updateOutput callback' , async ( ) = > {
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
2025-12-17 12:06:38 -05:00
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
2025-09-30 17:00:54 -04:00
if ( onActivity ) {
// Ensure calling activity doesn't crash when updateOutput is undefined
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'testAgent' ,
type : 'THOUGHT_CHUNK' ,
data : { text : 'Thinking silently.' } ,
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
// Execute without the optional callback
2026-04-10 10:11:17 -07:00
const result = await invocation . execute ( { abortSignal : signal } ) ;
2025-09-30 17:00:54 -04:00
expect ( result . error ) . toBeUndefined ( ) ;
2026-03-17 23:11:20 -04:00
const display = result . returnDisplay as SubagentProgress ;
expect ( display . isSubagentProgress ) . toBe ( true ) ;
expect ( display . state ) . toBe ( 'completed' ) ;
expect ( display . result ) . toBe ( 'Done' ) ;
2025-09-30 17:00:54 -04:00
} ) ;
it ( 'should handle executor run failure' , async ( ) = > {
const error = new Error ( 'Model failed during execution.' ) ;
mockExecutorInstance . run . mockRejectedValue ( error ) ;
2026-04-10 10:11:17 -07:00
const result = await invocation . execute ( {
abortSignal : signal ,
updateOutput ,
} ) ;
2025-09-30 17:00:54 -04:00
2026-03-02 21:04:31 +00:00
expect ( result . error ) . toBeUndefined ( ) ;
2025-09-30 17:00:54 -04:00
expect ( result . llmContent ) . toBe (
` Subagent 'MockAgent' failed. Error: ${ error . message } ` ,
) ;
2026-03-02 21:04:31 +00:00
const display = result . returnDisplay as SubagentProgress ;
expect ( display . isSubagentProgress ) . toBe ( true ) ;
expect ( display . recentActivity ) . toContainEqual (
expect . objectContaining ( {
type : 'thought' ,
content : ` Error: ${ error . message } ` ,
status : 'error' ,
} ) ,
) ;
2025-09-30 17:00:54 -04:00
} ) ;
it ( 'should handle executor creation failure' , async ( ) = > {
const creationError = new Error ( 'Failed to initialize tools.' ) ;
2025-12-17 12:06:38 -05:00
MockLocalAgentExecutor . create . mockRejectedValue ( creationError ) ;
2025-09-30 17:00:54 -04:00
2026-04-10 10:11:17 -07:00
const result = await invocation . execute ( {
abortSignal : signal ,
updateOutput ,
} ) ;
2025-09-30 17:00:54 -04:00
expect ( mockExecutorInstance . run ) . not . toHaveBeenCalled ( ) ;
2026-03-02 21:04:31 +00:00
expect ( result . error ) . toBeUndefined ( ) ;
expect ( result . llmContent ) . toContain ( creationError . message ) ;
const display = result . returnDisplay as SubagentProgress ;
expect ( display . recentActivity ) . toContainEqual (
expect . objectContaining ( {
content : ` Error: ${ creationError . message } ` ,
status : 'error' ,
} ) ,
) ;
2025-09-30 17:00:54 -04:00
} ) ;
it ( 'should handle abortion signal during execution' , async ( ) = > {
const abortError = new Error ( 'Aborted' ) ;
2026-03-02 21:04:31 +00:00
abortError . name = 'AbortError' ;
2025-09-30 17:00:54 -04:00
mockExecutorInstance . run . mockRejectedValue ( abortError ) ;
const controller = new AbortController ( ) ;
2026-04-10 10:11:17 -07:00
const executePromise = invocation . execute ( {
abortSignal : controller.signal ,
2025-09-30 17:00:54 -04:00
updateOutput ,
2026-04-10 10:11:17 -07:00
} ) ;
2025-09-30 17:00:54 -04:00
controller . abort ( ) ;
2026-03-02 21:04:31 +00:00
await expect ( executePromise ) . rejects . toThrow ( 'Aborted' ) ;
2025-09-30 17:00:54 -04:00
expect ( mockExecutorInstance . run ) . toHaveBeenCalledWith (
params ,
controller . signal ,
) ;
2026-03-02 21:04:31 +00:00
} ) ;
it ( 'should throw an error and bubble cancellation when execution returns ABORTED' , async ( ) = > {
const mockOutput = {
result : 'Cancelled by user' ,
terminate_reason : AgentTerminateMode.ABORTED ,
} ;
mockExecutorInstance . run . mockResolvedValue ( mockOutput ) ;
2026-04-10 10:11:17 -07:00
await expect (
invocation . execute ( { abortSignal : signal , updateOutput } ) ,
) . rejects . toThrow ( 'Operation cancelled by user' ) ;
2025-09-30 17:00:54 -04:00
} ) ;
2026-03-31 17:54:22 -04:00
it ( 'should publish SUBAGENT_ACTIVITY events to the MessageBus' , async ( ) = > {
const { MessageBusType } = await import ( '../confirmation-bus/types.js' ) ;
mockExecutorInstance . run . mockImplementation ( async ( ) = > {
const onActivity = MockLocalAgentExecutor . create . mock . calls [ 0 ] [ 2 ] ;
if ( onActivity ) {
onActivity ( {
isSubagentActivityEvent : true ,
agentName : 'MockAgent' ,
type : 'THOUGHT_CHUNK' ,
data : { text : 'Thinking...' } ,
} as SubagentActivityEvent ) ;
}
return { result : 'Done' , terminate_reason : AgentTerminateMode.GOAL } ;
} ) ;
2026-04-10 10:11:17 -07:00
await invocation . execute ( { abortSignal : signal , updateOutput } ) ;
2026-03-31 17:54:22 -04:00
expect ( mockMessageBus . publish ) . toHaveBeenCalledWith (
expect . objectContaining ( {
type : MessageBusType . SUBAGENT_ACTIVITY ,
subagentName : 'Mock Agent' ,
activity : expect.objectContaining ( {
type : 'thought' ,
content : 'Thinking...' ,
} ) ,
} ) ,
) ;
} ) ;
2025-09-30 17:00:54 -04:00
} ) ;
} ) ;