@@ -182,6 +182,53 @@ describe("shouldAbortForStall — execution-watchdog-exempt tools do not pin for
182182 ) . toBe ( true ) ;
183183 } ) ;
184184
185+ // ask_director is bounded, not exempt (CL-10206): the primary never mounts
186+ // it, so the name is a guard, and a stray mount must notice, then abort,
187+ // like wait_agents. Both shapes the turn state can hold are pinned: the
188+ // announced call, and a leftover tracked only per-id after a sibling
189+ // cleared currentToolName and the shared callIdByName slot.
190+ test . each ( [
191+ {
192+ representation : "direct" ,
193+ currentToolName : "ask_director" ,
194+ activeToolCalls : [ "ask-1" ] ,
195+ callIdByName : { ask_director : "ask-1" } ,
196+ callNameById : { "ask-1" : "ask_director" } ,
197+ } ,
198+ {
199+ representation : "mapping-only" ,
200+ currentToolName : null ,
201+ activeToolCalls : [ "ask-1" ] ,
202+ callIdByName : { } ,
203+ callNameById : { "ask-1" : "ask_director" } ,
204+ } ,
205+ ] ) (
206+ "in-flight ask_director ($representation) is quiet, then notices, then aborts, and every surface agrees" ,
207+ ( ask ) => {
208+ const inFlightAsk = {
209+ ...collect ,
210+ ...ask ,
211+ streamingType : "tool" as const ,
212+ stallNoticeMs : STALL_NOTICE_MS ,
213+ repeating : false ,
214+ } ;
215+ const points = [
216+ { nowMs : STALL_NOTICE_MS - 1 , level : "quiet" } ,
217+ { nowMs : STALL_NOTICE_MS , level : "notice" } ,
218+ { nowMs : STALL_TIMEOUT_MS - 1 , level : "notice" } ,
219+ { nowMs : STALL_TIMEOUT_MS , level : "abort" } ,
220+ ] as const ;
221+
222+ for ( const { nowMs, level } of points ) {
223+ const args = { ...inFlightAsk , nowMs } ;
224+ expect ( stallLevel ( args ) ) . toBe ( level ) ;
225+ expect ( shouldAbortForStall ( args ) ) . toBe ( level === "abort" ) ;
226+ expect ( shouldNoticeStall ( args ) ) . toBe ( level === "notice" ) ;
227+ expect ( isStalledForDisplay ( args ) ) . toBe ( level !== "quiet" ) ;
228+ }
229+ } ,
230+ ) ;
231+
185232 // tool.done of a sibling bash clears currentToolName and streamingType
186233 // while wait_agents is still in activeToolCalls. Keying only the last
187234 // name would leave that poll unbounded forever.
0 commit comments