@@ -30,7 +30,9 @@ const SUBJECT_MAX_CHARS: usize = 160;
3030const SENDER_MAX_CHARS : usize = 80 ;
3131const RECOVERY_POKE : & str = "[DING] unread st2 messages remain; check your inbox" ;
3232const PTY_COMMAND_TIMEOUT : Duration = Duration :: from_millis ( 600 ) ;
33+ #[ allow( dead_code) ]
3334const COMPOSER_OBSERVATION_WINDOW : Duration = Duration :: from_millis ( 450 ) ;
35+ #[ allow( dead_code) ]
3436const COMPOSER_OBSERVATION_POLL : Duration = Duration :: from_millis ( 10 ) ;
3537/// A human or active turn can keep a staged notice unsafe for minutes. Retrying `pty peek` every
3638/// inbox poll creates a short-lived child for each attempt, so keep the correctness fallback but
@@ -120,6 +122,20 @@ pub fn pty_submit_args(session: &str) -> Vec<String> {
120122 ]
121123}
122124
125+ /// Recovery delivery: one bounded PTY transaction containing paste and Return.
126+ pub fn pty_delivery_args ( session : & str , text : & str ) -> Vec < String > {
127+ vec ! [
128+ "send" . into( ) ,
129+ session. into( ) ,
130+ "--seq" . into( ) ,
131+ bracketed_paste( text) ,
132+ "--seq" . into( ) ,
133+ "key:return" . into( ) ,
134+ "--with-delay" . into( ) ,
135+ "500" . into( ) ,
136+ ]
137+ }
138+
123139/// One delivery attempt either submitted the notice, owns a paste that must be retried by
124140/// inspection only, or performed no input because the target was not positively safe.
125141#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
@@ -192,14 +208,13 @@ impl PtyPoker {
192208 text : & str ,
193209 before_submit : & mut dyn FnMut ( ) -> anyhow:: Result < ( ) > ,
194210 ) -> anyhow:: Result < PokeOutcome > {
195- observed_poke (
196- text,
197- & mut || self . peek ( ) ,
198- & mut || self . run ( pty_stage_args ( & self . session , text) , "send" ) ,
199- & mut || self . run ( pty_submit_args ( & self . session ) , "send" ) ,
200- & mut || thread:: sleep ( COMPOSER_OBSERVATION_POLL ) ,
201- before_submit,
202- )
211+ // Recovery contract: PTY-alive delivery is transport-first. Keep one owned payload,
212+ // stage it once, wait a bounded interval, then submit exactly once; pane heuristics remain
213+ // diagnostic only and must not silently suppress messaging.
214+ self . run ( pty_delivery_args ( & self . session , text) , "send" )
215+ . map_err ( |error| anyhow:: anyhow!( "staging DING payload: {error}" ) ) ?;
216+ before_submit ( ) ?;
217+ Ok ( PokeOutcome :: Delivered )
203218 }
204219}
205220
@@ -209,12 +224,10 @@ impl Poker for PtyPoker {
209224 }
210225
211226 fn retry_staged ( & self , text : & str ) -> anyhow:: Result < PokeOutcome > {
212- observed_retry_staged (
213- text,
214- & mut || self . peek ( ) ,
215- & mut || self . run ( pty_submit_args ( & self . session ) , "send" ) ,
216- & mut || Ok ( ( ) ) ,
217- )
227+ let _ = text;
228+ self . run ( pty_submit_args ( & self . session ) , "send" )
229+ . map_err ( |error| anyhow:: anyhow!( "submitting staged DING payload: {error}" ) ) ?;
230+ Ok ( PokeOutcome :: Delivered )
218231 }
219232
220233 fn adopt_staged ( & self , candidates : & [ String ] ) -> anyhow:: Result < Option < String > > {
@@ -302,6 +315,7 @@ fn exact_staged_candidate(screen: &str, candidates: &[String]) -> Option<String>
302315 } )
303316}
304317
318+ #[ allow( dead_code) ]
305319fn observed_poke (
306320 text : & str ,
307321 peek : & mut dyn FnMut ( ) -> anyhow:: Result < String > ,
@@ -413,6 +427,7 @@ fn submit_after_final_observation(
413427}
414428
415429/// Inspect-only retry for a payload whose paste command already started.
430+ #[ allow( dead_code) ]
416431fn observed_retry_staged (
417432 text : & str ,
418433 peek : & mut dyn FnMut ( ) -> anyhow:: Result < String > ,
0 commit comments