@@ -242,7 +242,7 @@ describe("a run of identical calls", () => {
242242 expect ( rows [ 0 ] ?. outstanding ) . toBe ( 0 ) ;
243243 } ) ;
244244
245- test ( "a lane keeps every member id while the count climbs " , ( ) => {
245+ test ( "a lane caps member ids and labels together at the run cap " , ( ) => {
246246 const rows : StreamRow [ ] = [ ] ;
247247 for ( let i = 1 ; i <= 33 ; i ++ ) {
248248 pushToolCall ( rows , {
@@ -253,7 +253,10 @@ describe("a run of identical calls", () => {
253253 }
254254 expect ( rows . length ) . toBe ( 1 ) ;
255255 expect ( rows [ 0 ] ?. callCount ) . toBe ( 33 ) ;
256- expect ( rows [ 0 ] ?. memberIds ?. length ) . toBe ( 33 ) ;
256+ // The lane's memory is bounded alongside the detail cap, oldest-first
257+ // like the answers — and both arrays together, never one alone.
258+ expect ( rows [ 0 ] ?. memberIds ?. length ) . toBe ( 30 ) ;
259+ expect ( rows [ 0 ] ?. memberLabels ?. length ) . toBe ( 30 ) ;
257260 expect ( rows [ 0 ] ?. memberIds ?. [ 0 ] ) . toBe ( "c1" ) ;
258261 expect ( pendingCallIndex ( rows , "grep" , "c33" ) ) . toBe ( 0 ) ;
259262 expect ( pendingCallIndex ( rows , "grep" , "c1" ) ) . toBe ( 0 ) ;
@@ -274,16 +277,24 @@ describe("a run of identical calls", () => {
274277 expect ( rows . length ) . toBe ( 1 ) ;
275278 expect ( rows [ 0 ] ?. pending ) . toBe ( true ) ;
276279 expect ( rows [ 0 ] ?. outstanding ) . toBe ( 32 ) ;
277- for ( let i = 2 ; i <= 33 ; i ++ ) {
280+ // Members past the run cap kept no id on the lane, so their answers
281+ // cannot pair back to it — except the newest, found by the lane's own id.
282+ for ( let i = 2 ; i <= 30 ; i ++ ) {
278283 pushToolResult ( rows , {
279284 name : "grep" ,
280285 content : `r${ i } ` ,
281286 callId : `c${ i } ` ,
282287 } ) ;
283288 }
289+ pushToolResult ( rows , { name : "grep" , content : "r33" , callId : "c33" } ) ;
284290 expect ( rows . length ) . toBe ( 1 ) ;
285- expect ( rows [ 0 ] ?. pending ) . toBeUndefined ( ) ;
286- expect ( rows [ 0 ] ?. outstanding ) . toBe ( 0 ) ;
291+ expect ( rows [ 0 ] ?. pending ) . toBe ( true ) ;
292+ expect ( rows [ 0 ] ?. outstanding ) . toBe ( 2 ) ;
293+ pushToolResult ( rows , { name : "grep" , content : "r31" , callId : "c31" } ) ;
294+ pushToolResult ( rows , { name : "grep" , content : "r32" , callId : "c32" } ) ;
295+ expect ( rows . length ) . toBe ( 3 ) ;
296+ expect ( rows [ 0 ] ?. pending ) . toBe ( true ) ;
297+ expect ( rows [ 0 ] ?. outstanding ) . toBe ( 2 ) ;
287298 } ) ;
288299
289300 test ( "a different tool breaks the lane" , ( ) => {
@@ -333,6 +344,180 @@ describe("a run of identical calls", () => {
333344 expect ( rows [ 0 ] ?. outstanding ) . toBe ( 1 ) ;
334345 expect ( rows [ 0 ] ?. pending ) . toBe ( true ) ;
335346 } ) ;
347+
348+ const runLines = ( row : StreamRow | undefined ) : string [ ] =>
349+ ( row ?. detail ?? [ ] ) . map ( ( line ) =>
350+ line . map ( ( segment ) => segment . text ) . join ( "" ) ,
351+ ) ;
352+
353+ test ( "each member line names the call's own target, not a bare answer" , ( ) => {
354+ const rows : StreamRow [ ] = [ ] ;
355+ pushToolCall ( rows , {
356+ name : "mcp__linear__save_comment" ,
357+ arguments : JSON . stringify ( { issueId : "CL-7386" , body : "looks good" } ) ,
358+ callId : "m1" ,
359+ } ) ;
360+ pushToolCall ( rows , {
361+ name : "mcp__linear__save_comment" ,
362+ arguments : JSON . stringify ( { issueId : "CL-7390" , body : "done" } ) ,
363+ callId : "m2" ,
364+ } ) ;
365+ pushToolResult ( rows , {
366+ name : "mcp__linear__save_comment" ,
367+ content : "" ,
368+ callId : "m1" ,
369+ } ) ;
370+ pushToolResult ( rows , {
371+ name : "mcp__linear__save_comment" ,
372+ content : "" ,
373+ callId : "m2" ,
374+ } ) ;
375+ // An MCP call's painted summary is empty — the verb is the sentence — so
376+ // the lane reads the identifying argument (the issue, not the body).
377+ expect ( rows [ 0 ] ?. memberLabels ) . toEqual ( [ "CL-7386" , "CL-7390" ] ) ;
378+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( [
379+ "CL-7386 — answered" ,
380+ "CL-7390 — answered" ,
381+ ] ) ;
382+ } ) ;
383+
384+ test ( "four members each name their own target" , ( ) => {
385+ const rows : StreamRow [ ] = [ ] ;
386+ const issues = [ "CL-7386" , "CL-7390" , "CL-7399" , "CL-7401" ] ;
387+ issues . forEach ( ( issueId , i ) => {
388+ pushToolCall ( rows , {
389+ name : "mcp__linear__save_comment" ,
390+ arguments : JSON . stringify ( { issueId, body : `note ${ i } ` } ) ,
391+ callId : `m${ i } ` ,
392+ } ) ;
393+ } ) ;
394+ issues . forEach ( ( _ , i ) => {
395+ pushToolResult ( rows , {
396+ name : "mcp__linear__save_comment" ,
397+ content : "" ,
398+ callId : `m${ i } ` ,
399+ } ) ;
400+ } ) ;
401+ expect ( rows . length ) . toBe ( 1 ) ;
402+ expect ( rows [ 0 ] ?. memberLabels ) . toEqual ( issues ) ;
403+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( issues . map ( ( id ) => `${ id } — answered` ) ) ;
404+ } ) ;
405+
406+ test ( "a pre-PR lane with ids but no labels coalesces with aligned placeholders" , ( ) => {
407+ const rows : StreamRow [ ] = [ ] ;
408+ pushToolCall ( rows , {
409+ name : "mcp__linear__save_comment" ,
410+ arguments : JSON . stringify ( { issueId : "CL-7386" , body : "first" } ) ,
411+ callId : "m1" ,
412+ } ) ;
413+ pushToolCall ( rows , {
414+ name : "mcp__linear__save_comment" ,
415+ arguments : JSON . stringify ( { issueId : "CL-7390" , body : "second" } ) ,
416+ callId : "m2" ,
417+ } ) ;
418+ // Lanes persisted before per-call labels carry memberIds without
419+ // memberLabels; only the tail's own label is still recoverable.
420+ const { memberLabels : _dropped , ...legacy } = defined ( rows [ 0 ] ) ;
421+ rows [ 0 ] = legacy ;
422+ pushToolCall ( rows , {
423+ name : "mcp__linear__save_comment" ,
424+ arguments : JSON . stringify ( { issueId : "CL-7399" , body : "third" } ) ,
425+ callId : "m3" ,
426+ } ) ;
427+ expect ( rows [ 0 ] ?. memberIds ) . toEqual ( [ "m1" , "m2" , "m3" ] ) ;
428+ expect ( rows [ 0 ] ?. memberLabels ) . toEqual ( [ "" , "CL-7390" , "CL-7399" ] ) ;
429+ pushToolResult ( rows , {
430+ name : "mcp__linear__save_comment" ,
431+ content : "" ,
432+ callId : "m2" ,
433+ } ) ;
434+ // Without the placeholder backfill the second result would read the
435+ // third member's label.
436+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( [ "CL-7390 — answered" ] ) ;
437+ pushToolResult ( rows , {
438+ name : "mcp__linear__save_comment" ,
439+ content : "" ,
440+ callId : "m3" ,
441+ } ) ;
442+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( [
443+ "CL-7390 — answered" ,
444+ "CL-7399 — answered" ,
445+ ] ) ;
446+ } ) ;
447+
448+ test ( "a member settled before the lane keeps the call's label, not the payload's" , ( ) => {
449+ const rows : StreamRow [ ] = [ ] ;
450+ pushToolCall ( rows , {
451+ name : "mcp__linear__save_comment" ,
452+ arguments : JSON . stringify ( { issueId : "CL-7386" , body : "first" } ) ,
453+ callId : "m1" ,
454+ } ) ;
455+ pushToolResult ( rows , {
456+ name : "mcp__linear__save_comment" ,
457+ content : '{"id":"comment-9"}' ,
458+ callId : "m1" ,
459+ } ) ;
460+ // The merge replaced the row's args with the answer payload; the label
461+ // must still name what the call acted on.
462+ pushToolCall ( rows , {
463+ name : "mcp__linear__save_comment" ,
464+ arguments : JSON . stringify ( { issueId : "CL-7390" , body : "second" } ) ,
465+ callId : "m2" ,
466+ } ) ;
467+ expect ( rows [ 0 ] ?. memberLabels ) . toEqual ( [ "CL-7386" , "CL-7390" ] ) ;
468+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( [ 'CL-7386 — {"id":"comment-9"}' ] ) ;
469+ } ) ;
470+
471+ test ( "a failed member's line carries its target and the error" , ( ) => {
472+ const rows : StreamRow [ ] = [ ] ;
473+ pushToolCall ( rows , {
474+ name : "mcp__linear__save_comment" ,
475+ arguments : JSON . stringify ( { issueId : "CL-1" , body : "x" } ) ,
476+ callId : "m1" ,
477+ } ) ;
478+ pushToolCall ( rows , {
479+ name : "mcp__linear__save_comment" ,
480+ arguments : JSON . stringify ( { issueId : "CL-2" , body : "y" } ) ,
481+ callId : "m2" ,
482+ } ) ;
483+ pushToolResult ( rows , {
484+ name : "mcp__linear__save_comment" ,
485+ content : "HTTP 401 unauthorized" ,
486+ isError : true ,
487+ callId : "m1" ,
488+ } ) ;
489+ pushToolResult ( rows , {
490+ name : "mcp__linear__save_comment" ,
491+ content : "" ,
492+ callId : "m2" ,
493+ } ) ;
494+ expect ( runLines ( rows [ 0 ] ) ) . toEqual ( [
495+ "CL-1 — HTTP 401 unauthorized" ,
496+ "CL-2 — answered" ,
497+ ] ) ;
498+ } ) ;
499+
500+ test ( "member labels are one-line and bounded" , ( ) => {
501+ const rows : StreamRow [ ] = [ ] ;
502+ pushToolCall ( rows , {
503+ name : "mcp__granola__search" ,
504+ arguments : JSON . stringify ( {
505+ query : ` multi\n line ${ "q" . repeat ( 100 ) } ` ,
506+ } ) ,
507+ callId : "m1" ,
508+ } ) ;
509+ pushToolCall ( rows , {
510+ name : "mcp__granola__search" ,
511+ arguments : JSON . stringify ( { query : "b" } ) ,
512+ callId : "m2" ,
513+ } ) ;
514+ const labels = rows [ 0 ] ?. memberLabels ?? [ ] ;
515+ expect ( labels . length ) . toBe ( 2 ) ;
516+ expect ( labels [ 0 ] ?. length ) . toBeLessThanOrEqual ( 48 ) ;
517+ expect ( labels [ 0 ] ) . not . toContain ( "\n" ) ;
518+ expect ( labels [ 0 ] ?. endsWith ( "…" ) ) . toBe ( true ) ;
519+ expect ( labels [ 1 ] ) . toBe ( "b" ) ;
520+ } ) ;
336521} ) ;
337522
338523describe ( "parallel calls to the same tool" , ( ) => {
@@ -969,8 +1154,7 @@ describe("lane paint", () => {
9691154 const answers = ( rows [ 0 ] ?. detail ?? [ ] ) . map ( ( line ) =>
9701155 line . map ( ( segment ) => segment . text ) . join ( "" ) ,
9711156 ) ;
972- expect ( answers ) . not . toEqual ( [ "answered" , "answered" ] ) ;
973- expect ( answers ) . toContain ( "a" ) ;
1157+ expect ( answers ) . toEqual ( [ "echo a — a" , "echo b — b" ] ) ;
9741158 expect ( rows [ 0 ] ?. resultText ) . toBe ( "b" ) ;
9751159 expect ( rows [ 0 ] ?. previewLines ) . toEqual ( [ "b" ] ) ;
9761160 } ) ;
0 commit comments