@@ -484,6 +484,102 @@ describe("attemptCompletionTool", () => {
484484 } )
485485
486486 describe ( "completion lifecycle" , ( ) => {
487+ it ( "delegates an active subtask completion only when the parent is awaiting that child" , async ( ) => {
488+ const block : AttemptCompletionToolUse = {
489+ type : "tool_use" ,
490+ name : "attempt_completion" ,
491+ params : { result : "9" } ,
492+ nativeArgs : { result : "9" } ,
493+ partial : false ,
494+ }
495+ const mockProvider = {
496+ getTaskWithId : vi . fn ( ) . mockImplementation ( ( id : string ) => {
497+ if ( id === "child-1" ) {
498+ return Promise . resolve ( { historyItem : { id, status : "active" } } )
499+ }
500+ if ( id === "parent-1" ) {
501+ return Promise . resolve ( {
502+ historyItem : { id, status : "delegated" , awaitingChildId : "child-1" } ,
503+ } )
504+ }
505+ throw new Error ( `unexpected task id ${ id } ` )
506+ } ) ,
507+ reopenParentFromDelegation : vi . fn ( ) . mockResolvedValue ( undefined ) ,
508+ }
509+
510+ Object . assign ( mockTask , {
511+ taskId : "child-1" ,
512+ parentTaskId : "parent-1" ,
513+ providerRef : { deref : ( ) => mockProvider } ,
514+ } )
515+ mockAskFinishSubTaskApproval . mockResolvedValue ( true )
516+
517+ const callbacks : AttemptCompletionCallbacks = {
518+ askApproval : mockAskApproval ,
519+ handleError : mockHandleError ,
520+ pushToolResult : mockPushToolResult ,
521+ askFinishSubTaskApproval : mockAskFinishSubTaskApproval ,
522+ toolDescription : mockToolDescription ,
523+ }
524+
525+ await attemptCompletionTool . handle ( mockTask as Task , block , callbacks )
526+
527+ expect ( mockAskFinishSubTaskApproval ) . toHaveBeenCalled ( )
528+ expect ( mockProvider . reopenParentFromDelegation ) . toHaveBeenCalledWith ( {
529+ parentTaskId : "parent-1" ,
530+ childTaskId : "child-1" ,
531+ completionResultSummary : "9" ,
532+ } )
533+ expect ( mockTask . ask ) . not . toHaveBeenCalled ( )
534+ expect ( mockPushToolResult ) . toHaveBeenCalledWith ( "" )
535+ } )
536+
537+ it ( "does not delegate a lineage-preserving subtask when the parent is no longer awaiting it" , async ( ) => {
538+ const block : AttemptCompletionToolUse = {
539+ type : "tool_use" ,
540+ name : "attempt_completion" ,
541+ params : { result : "9" } ,
542+ nativeArgs : { result : "9" } ,
543+ partial : false ,
544+ }
545+ const mockProvider = {
546+ getTaskWithId : vi . fn ( ) . mockImplementation ( ( id : string ) => {
547+ if ( id === "child-1" ) {
548+ return Promise . resolve ( { historyItem : { id, status : "active" } } )
549+ }
550+ if ( id === "parent-1" ) {
551+ return Promise . resolve ( {
552+ historyItem : { id, status : "active" , awaitingChildId : undefined } ,
553+ } )
554+ }
555+ throw new Error ( `unexpected task id ${ id } ` )
556+ } ) ,
557+ reopenParentFromDelegation : vi . fn ( ) . mockResolvedValue ( undefined ) ,
558+ }
559+
560+ Object . assign ( mockTask , {
561+ taskId : "child-1" ,
562+ parentTaskId : "parent-1" ,
563+ providerRef : { deref : ( ) => mockProvider } ,
564+ } )
565+ mockAskFinishSubTaskApproval . mockResolvedValue ( true )
566+
567+ const callbacks : AttemptCompletionCallbacks = {
568+ askApproval : mockAskApproval ,
569+ handleError : mockHandleError ,
570+ pushToolResult : mockPushToolResult ,
571+ askFinishSubTaskApproval : mockAskFinishSubTaskApproval ,
572+ toolDescription : mockToolDescription ,
573+ }
574+
575+ await attemptCompletionTool . handle ( mockTask as Task , block , callbacks )
576+
577+ expect ( mockAskFinishSubTaskApproval ) . not . toHaveBeenCalled ( )
578+ expect ( mockProvider . reopenParentFromDelegation ) . not . toHaveBeenCalled ( )
579+ expect ( mockTask . ask ) . toHaveBeenCalledWith ( "completion_result" , "" , false )
580+ expect ( mockCaptureTaskCompleted ) . toHaveBeenCalledWith ( "child-1" )
581+ } )
582+
487583 it ( "emits TaskCompleted only when completion is accepted" , async ( ) => {
488584 const block : AttemptCompletionToolUse = {
489585 type : "tool_use" ,
0 commit comments