@@ -386,11 +386,12 @@ private static List<QueuedRunMessage> TakenAsides(ChatDetails chat, Guid? head,
386386 }
387387
388388 /// <summary>
389- /// A teammate whose turn stopped for lack of progress tells the lead, who would otherwise learn
390- /// of it only by looking. A teammate's branch is one that a team message of the main branch
389+ /// A teammate whose turn stopped — for lack of progress, or failed — tells the lead, who would
390+ /// otherwise learn of it only by looking, and a team waits on a lead that does not look. A
391+ /// teammate's branch is one that a team message of the main branch
391392 /// started (docs/34-asides-and-team-messages.md); any other branch keeps the report to itself.
392393 /// </summary>
393- private async Task ReportStalledTeammateAsync ( Runtime runtime , string report , CancellationToken token )
394+ private async Task ReportToLeadAsync ( Runtime runtime , string report , CancellationToken token )
394395 {
395396 var ( projectId , chatId , branchId ) = ( runtime . State . ProjectId , runtime . State . ChatId , runtime . State . BranchId ) ;
396397 if ( branchId == chatId ) return ;
@@ -405,7 +406,7 @@ private async Task ReportStalledTeammateAsync(Runtime runtime, string report, Ca
405406 try
406407 {
407408 await SubmitCoreAsync ( projectId , chatId , new SubmitChatMessageRequest ( id , id ,
408- "Stopped: several steps in a row produced nothing new. \n \n " + report , ChatSubmitMode . Send , chatId ) ,
409+ report , ChatSubmitMode . Send , chatId ) ,
409410 true , token , new ChatMessageSender ( chatId , branchId , "blocker" ) ) ;
410411 }
411412 catch ( Exception error ) when ( error is InvalidOperationException or ArgumentException or IOException
@@ -695,7 +696,8 @@ await contextBuilder.BuildAsync(chat, ResumeHead(chat, runtime.State.BranchId, q
695696 draftToolCall : ( name , ct ) => ReportDraftToolCallAsync ( runtime , name , ct ) ,
696697 overlayPromptsAllowed : queued . Interactive ,
697698 asides : ct => TakeAsidesIntoTurnAsync ( runtime , ct ) ,
698- stalledReport : ( report , ct ) => ReportStalledTeammateAsync ( runtime , report , ct ) ) ;
699+ stalledReport : ( report , ct ) => ReportToLeadAsync ( runtime ,
700+ "Stopped: several steps in a row produced nothing new.\n \n " + report , ct ) ) ;
699701 var suggestTitle = false ;
700702 Guid ? answeredHead = null ;
701703 using ( await synchronization . EnterAsync ( runtime . State . ChatId , token ) )
@@ -771,6 +773,15 @@ await contextBuilder.BuildAsync(chat, ResumeHead(chat, runtime.State.BranchId, q
771773 try { await chatMutations . MarkActivityCoreAsync ( runtime . State . ProjectId , runtime . State . ChatId , CancellationToken . None ) ; }
772774 catch ( Exception markError ) when ( markError is IOException or UnauthorizedAccessException
773775 or InvalidOperationException or Domain . Common . DomainException ) { }
776+ // Started outside this lease: telling the lead submits into the main branch, which
777+ // takes the same chat lock.
778+ if ( ! _shutdown . IsCancellationRequested )
779+ {
780+ var failure = Describe ( error ) ;
781+ _ = Task . Run ( ( ) => ReportToLeadAsync ( runtime ,
782+ $ "Failed: { failure } \n The branch is stopped until someone retries or discards its turn.",
783+ _shutdown . Token ) ) ;
784+ }
774785 }
775786 if ( error is OperationCanceledException && runtime . ResumeRequested && ! _shutdown . IsCancellationRequested ) runtime . State . Resume ( ) ;
776787 try { await SaveAsync ( runtime , await chats . GetAsync ( runtime . State . ProjectId , runtime . State . ChatId , CancellationToken . None ) , CancellationToken . None ) ; }
0 commit comments