@@ -117,6 +117,7 @@ function buildPreparedCliRunContext(params: {
117117 skillsSnapshot ?: PreparedCliRunContext [ "params" ] [ "skillsSnapshot" ] ;
118118 thinkLevel ?: PreparedCliRunContext [ "params" ] [ "thinkLevel" ] ;
119119 executionMode ?: PreparedCliRunContext [ "params" ] [ "executionMode" ] ;
120+ emitCommentaryText ?: boolean ;
120121 workspaceDir ?: string ;
121122 timeoutMs ?: number ;
122123} ) : PreparedCliRunContext {
@@ -187,6 +188,7 @@ function buildPreparedCliRunContext(params: {
187188 model : params . model ,
188189 thinkLevel : params . thinkLevel ,
189190 executionMode : params . executionMode ,
191+ emitCommentaryText : params . emitCommentaryText ,
190192 timeoutMs : params . timeoutMs ?? 1_000 ,
191193 runId : params . runId ,
192194 skillsSnapshot : params . skillsSnapshot ,
@@ -1259,6 +1261,91 @@ describe("runCliAgent spawn path", () => {
12591261 }
12601262 } ) ;
12611263
1264+ it ( "keeps pre-tool commentary out of an empty-result Claude live reply" , async ( ) => {
1265+ const agentEvents : Array < { stream : string ; data : unknown } > = [ ] ;
1266+ const stop = onAgentEvent ( ( event ) => {
1267+ agentEvents . push ( { stream : event . stream , data : event . data } ) ;
1268+ } ) ;
1269+ let stdoutListener : ( ( chunk : string ) => void ) | undefined ;
1270+ const stdin = {
1271+ write : vi . fn ( ( _data : string , callback ?: ( error ?: Error | null ) => void ) => {
1272+ stdoutListener ?.(
1273+ [
1274+ JSON . stringify ( { type : "system" , subtype : "init" , session_id : "live-empty-result" } ) ,
1275+ JSON . stringify ( {
1276+ type : "stream_event" ,
1277+ event : {
1278+ type : "content_block_delta" ,
1279+ delta : { type : "text_delta" , text : "Let me check." } ,
1280+ } ,
1281+ } ) ,
1282+ JSON . stringify ( {
1283+ type : "stream_event" ,
1284+ event : {
1285+ type : "content_block_start" ,
1286+ index : 1 ,
1287+ content_block : { type : "tool_use" , id : "tool-1" , name : "Read" , input : { } } ,
1288+ } ,
1289+ } ) ,
1290+ JSON . stringify ( {
1291+ type : "stream_event" ,
1292+ event : {
1293+ type : "content_block_delta" ,
1294+ delta : { type : "text_delta" , text : "Final answer." } ,
1295+ } ,
1296+ } ) ,
1297+ JSON . stringify ( {
1298+ type : "result" ,
1299+ session_id : "live-empty-result" ,
1300+ result : "" ,
1301+ } ) ,
1302+ ] . join ( "\n" ) + "\n" ,
1303+ ) ;
1304+ callback ?.( ) ;
1305+ } ) ,
1306+ end : vi . fn ( ) ,
1307+ } ;
1308+ supervisorSpawnMock . mockImplementationOnce ( async ( ...args : unknown [ ] ) => {
1309+ const input = ( args [ 0 ] ?? { } ) as { onStdout ?: ( chunk : string ) => void } ;
1310+ stdoutListener = input . onStdout ;
1311+ return {
1312+ runId : "live-empty-result-run" ,
1313+ pid : 2345 ,
1314+ startedAtMs : Date . now ( ) ,
1315+ stdin,
1316+ wait : vi . fn ( ( ) => new Promise ( ( ) => { } ) ) ,
1317+ cancel : vi . fn ( ) ,
1318+ } ;
1319+ } ) ;
1320+
1321+ try {
1322+ const result = await executePreparedCliRun (
1323+ buildPreparedCliRunContext ( {
1324+ provider : "claude-cli" ,
1325+ model : "sonnet" ,
1326+ runId : "run-live-empty-result" ,
1327+ emitCommentaryText : true ,
1328+ backend : { liveSession : "claude-stdio" } ,
1329+ } ) ,
1330+ ) ;
1331+
1332+ expect ( result . text ) . toBe ( "Final answer." ) ;
1333+ expect ( agentEvents ) . toContainEqual ( {
1334+ stream : "item" ,
1335+ data : expect . objectContaining ( {
1336+ kind : "preamble" ,
1337+ progressText : "Let me check." ,
1338+ } ) ,
1339+ } ) ;
1340+ expect ( agentEvents ) . toContainEqual ( {
1341+ stream : "assistant" ,
1342+ data : { text : "Final answer." , delta : "Final answer." } ,
1343+ } ) ;
1344+ } finally {
1345+ stop ( ) ;
1346+ }
1347+ } ) ;
1348+
12621349 it ( "keeps non-capture live prepared backend cleanup with the whole-run owner" , async ( ) => {
12631350 let stdoutListener : ( ( chunk : string ) => void ) | undefined ;
12641351 const stdin = {
0 commit comments