@@ -1286,6 +1286,15 @@ describe("runHeartbeatOnce", () => {
12861286 replies : [ { text : "Because it helps" , isReasoning : true } , { text : "HEARTBEAT_OK" } ] ,
12871287 expectedTexts : [ "Thinking\n\n_Because it helps_" ] ,
12881288 } ,
1289+ {
1290+ // Reasoning-only result: the selector returns no main reply, but the
1291+ // documented includeReasoning opt-in must still deliver the Thinking
1292+ // message instead of going silent (#92242 follow-up / review finding).
1293+ name : "raw flagged reasoning only (no main reply)" ,
1294+ caseDir : "hb-reasoning-only" ,
1295+ replies : [ { text : "Because it helps" , isReasoning : true } ] ,
1296+ expectedTexts : [ "Thinking\n\n_Because it helps_" ] ,
1297+ } ,
12891298 {
12901299 name : "visible final that starts with thinking prose" ,
12911300 caseDir : "hb-thinking-visible-final" ,
@@ -1371,6 +1380,67 @@ describe("runHeartbeatOnce", () => {
13711380 } ,
13721381 ) ;
13731382
1383+ it ( "does not surface a trailing legacy reasoning payload as the reply when includeReasoning is unset" , async ( ) => {
1384+ // With includeReasoning unset, a legacy "Reasoning:"-prefixed payload after
1385+ // the final answer must not become the visible heartbeat reply, and no
1386+ // separate Thinking message is sent. (#92242 review follow-up)
1387+ const replySpy = vi . fn ( ) ;
1388+ try {
1389+ const tmpDir = await createCaseDir ( "hb-legacy-reasoning-unset" ) ;
1390+ const storePath = path . join ( tmpDir , "sessions.json" ) ;
1391+ const cfg : OpenClawConfig = {
1392+ agents : {
1393+ defaults : {
1394+ workspace : tmpDir ,
1395+ heartbeat : { every : "5m" , target : "whatsapp" } ,
1396+ } ,
1397+ } ,
1398+ channels : { whatsapp : { allowFrom : [ "*" ] } } ,
1399+ session : { store : storePath } ,
1400+ } ;
1401+ const sessionKey = resolveMainSessionKey ( cfg ) ;
1402+ await fs . writeFile (
1403+ storePath ,
1404+ JSON . stringify ( {
1405+ [ sessionKey ] : {
1406+ sessionId : "sid" ,
1407+ updatedAt : Date . now ( ) ,
1408+ lastChannel : "whatsapp" ,
1409+ lastProvider : "whatsapp" ,
1410+ 1411+ } ,
1412+ } ) ,
1413+ ) ;
1414+
1415+ replySpy . mockResolvedValue ( [
1416+ { text : "All clear" } ,
1417+ { text : "Reasoning: because nothing changed" } ,
1418+ ] ) ;
1419+ const sendWhatsApp = vi
1420+ . fn <
1421+ (
1422+ to : string ,
1423+ text : string ,
1424+ opts ?: unknown ,
1425+ ) => Promise < { messageId : string ; toJid : string } >
1426+ > ( )
1427+ . mockResolvedValue ( { messageId : "m1" , toJid : "jid" } ) ;
1428+
1429+ await runHeartbeatOnce ( {
1430+ cfg,
1431+ deps : createHeartbeatDeps ( sendWhatsApp , { getReplyFromConfig : replySpy } ) ,
1432+ } ) ;
1433+
1434+ expect ( sendWhatsApp ) . toHaveBeenCalledTimes ( 1 ) ;
1435+ expectWhatsAppSendCall ( sendWhatsApp , 0 , {
1436+ 1437+ text : "All clear" ,
1438+ } ) ;
1439+ } finally {
1440+ replySpy . mockReset ( ) ;
1441+ }
1442+ } ) ;
1443+
13741444 it ( "loads the default agent session from templated stores" , async ( ) => {
13751445 const tmpDir = await createCaseDir ( "openclaw-hb" ) ;
13761446 const storeTemplate = path . join ( tmpDir , "agents" , "{agentId}" , "sessions.json" ) ;
0 commit comments