@@ -49,7 +49,6 @@ import {
4949 repetitionRecoveryMessage ,
5050 isStalledForDisplay ,
5151 shouldAbortForStall ,
52- shouldAbortForStalledWakeTurn ,
5352 stallLevel ,
5453 STALL_NOTICE_MESSAGE ,
5554 STALL_NOTICE_MS ,
@@ -309,12 +308,11 @@ export interface SessionBridge {
309308 */
310309 flushMailboxMail : ( ) => void ;
311310 /**
312- * Stall bound for a silent ask-wake primary turn (CL-8016). Ends the turn
313- * through the same path as an operator stop when it is still silent past
314- * the stall threshold, then hands queued mail over and re-surfaces the
315- * still-pending questions (escalated) so neither the queue nor the parked
316- * asks freeze. Returns true when it aborted. Never touches tool-execution
317- * or gated turns; those are someone else's turn shape. Driven by the fleet
311+ * Stall bound for a silent ask-wake primary turn (CL-8016). If a wake was
312+ * actually sent (`askWakeTurnArmed`) and `shouldAbortForStall` says the
313+ * turn is silent past the bound (including awaiting-first-token, per
314+ * #1095), un-dedupe the delivered wakes and interrupt so the questions
315+ * re-surface escalated. Returns true when it aborted. Driven by the fleet
318316 * stall poll, which settles deadline-past asks first.
319317 */
320318 abortStalledWakeTurn : ( ) => boolean ;
@@ -1885,26 +1883,12 @@ export function attachSessionBridge(
18851883 bag . flushMailboxMail = flushMailboxMail ;
18861884
18871885 /**
1888- * Stall bound for a silent ask-wake primary turn (CL-8016). Only an armed
1889- * (wake-sent, never settled) turn can match: the predicate bounds silence
1890- * past the threshold while tool-execution and gated turns stay excluded.
1891- * The layer is stamped before the interrupt so the marker names the hung
1892- * layer instead of the post-abort idle. The turn ends through the same
1893- * path as an operator stop — interrupt the record, hand the queued mail
1894- * over, then let occupancy drive mail or re-surface the still-pending
1895- * questions (escalated) — so neither the queue nor the parked asks freeze.
1886+ * A stalled armed wake never landed: drop matching deliveredAskWake entries
1887+ * and bump the resurface count so the next flush restates the questions
1888+ * (escalated) instead of skipping them as already delivered.
18961889 */
1897- const abortStalledWakeTurn = ( ) : boolean => {
1898- if ( bag . disposed || ! bag . askWakeTurnArmed ) return false ;
1899- if ( ! shouldAbortForStalledWakeTurn ( stallArgsFor ( now ( ) ) ) ) return false ;
1900- const layer = turnStallLayer ( bag . turn ) ?? "mid-stream" ;
1901- // The aborted turn never landed its wakes: release the still-pending
1902- // questions from delivery dedupe so the trailing flush restates them
1903- // (escalated via the count), and bump the count so the restatement reads
1904- // as proof the earlier turn stalled rather than as a duplicate. Settled
1905- // or replaced questions are untouched — only a pending question whose
1906- // delivered id matches can re-surface, so each abort yields at most one
1907- // restatement per live question.
1890+ const unDedupeArmedAskWakes = ( ) : void => {
1891+ if ( ! bag . askWakeTurnArmed ) return ;
19081892 for ( const [ sessionId , ask ] of bag . pendingAskWake ) {
19091893 if ( bag . deliveredAskWake . get ( sessionId ) !== ask . questionId ) continue ;
19101894 bag . deliveredAskWake . delete ( sessionId ) ;
@@ -1913,6 +1897,21 @@ export function attachSessionBridge(
19131897 ( bag . askWakeResurface . get ( sessionId ) ?? 0 ) + 1 ,
19141898 ) ;
19151899 }
1900+ } ;
1901+
1902+ /**
1903+ * Stall bound for a silent ask-wake primary turn (CL-8016). Only an armed
1904+ * (wake-sent, never settled) turn can match. Silence uses `shouldAbortForStall`
1905+ * — the same #1095 bound that already covers awaiting-first-token — then
1906+ * un-dedupes and interrupts so parked questions re-surface. Mail first so
1907+ * occupancy can take the next turn; the trailing wake flush restates if
1908+ * mail did not start one.
1909+ */
1910+ const abortStalledWakeTurn = ( ) : boolean => {
1911+ if ( bag . disposed || ! bag . askWakeTurnArmed ) return false ;
1912+ if ( ! shouldAbortForStall ( stallArgsFor ( now ( ) ) ) ) return false ;
1913+ const layer = turnStallLayer ( bag . turn ) ?? "mid-stream" ;
1914+ unDedupeArmedAskWakes ( ) ;
19161915 recordTurnMarker ( bag , `stall-abort:${ layer } ` ) ;
19171916 bag . askWakeTurnArmed = false ;
19181917 bag . turn = turnStateOnInterrupt ( bag . turn , now ( ) ) ;
@@ -1943,8 +1942,9 @@ export function attachSessionBridge(
19431942 // turn the operator (or the watchdog) deliberately stopped.
19441943 recordLastSent ( null ) ;
19451944 bag . awaitingContinuationInference = false ;
1946- // An operator stop ends the turn the same way a settle does: no bound
1947- // is owed anymore, whatever the turn was started for.
1945+ // Armed wake that dies here (operator stop or #1095 stall abort) never
1946+ // landed: un-dedupe so flushPendingAskWake restates the questions.
1947+ unDedupeArmedAskWakes ( ) ;
19481948 bag . askWakeTurnArmed = false ;
19491949 bag . turn = turnStateOnInterrupt ( bag . turn , now ( ) ) ;
19501950 paintPhase ( ) ;
0 commit comments