@@ -256,6 +256,84 @@ describe("iMessage monitor last-route updates", () => {
256256 } ) ;
257257 } ) ;
258258
259+ it ( "keeps direct progress options when imsg lacks native typing support" , async ( ) => {
260+ setCachedIMessagePrivateApiStatus ( "imsg" , {
261+ available : true ,
262+ v2Ready : true ,
263+ selectors : { } ,
264+ rpcMethods : [ "watch.subscribe" , "send" , "read" ] ,
265+ } ) ;
266+ dispatchInboundMessageMock . mockImplementationOnce ( async ( params ) => {
267+ expect ( params . replyOptions ?. suppressDefaultToolProgressMessages ) . toBe ( true ) ;
268+ expect ( params . replyOptions ?. allowProgressCallbacksWhenSourceDeliverySuppressed ) . toBe ( true ) ;
269+ expect ( params . replyOptions ?. onToolStart ) . toBeUndefined ( ) ;
270+ return { queuedFinal : false , counts : { tool : 0 , block : 0 , final : 0 } } as const ;
271+ } ) ;
272+
273+ let onNotification : ( ( message : { method : string ; params : unknown } ) => void ) | undefined ;
274+ const client = {
275+ request : vi . fn ( async ( method : string ) => {
276+ if ( method === "watch.subscribe" ) {
277+ return { subscription : 1 } ;
278+ }
279+ if ( method === "typing" ) {
280+ throw new Error ( "typing should not start without native typing support" ) ;
281+ }
282+ throw new Error ( `unexpected imsg method ${ method } ` ) ;
283+ } ) ,
284+ waitForClose : vi . fn ( async ( ) => {
285+ onNotification ?.( {
286+ method : "message" ,
287+ params : {
288+ message : {
289+ id : 13 ,
290+ chat_id : 123 ,
291+ sender : "+15550001111" ,
292+ is_from_me : false ,
293+ text : "run a long script without native typing" ,
294+ is_group : false ,
295+ created_at : new Date ( ) . toISOString ( ) ,
296+ } ,
297+ } ,
298+ } ) ;
299+ await Promise . resolve ( ) ;
300+ await Promise . resolve ( ) ;
301+ } ) ,
302+ stop : vi . fn ( async ( ) => { } ) ,
303+ } ;
304+ createIMessageRpcClientMock . mockImplementation ( async ( params ) => {
305+ if ( ! params ?. onNotification ) {
306+ throw new Error ( "expected iMessage notification handler" ) ;
307+ }
308+ onNotification = params . onNotification ;
309+ return client as never ;
310+ } ) ;
311+
312+ await monitorIMessageProvider ( {
313+ config : {
314+ channels : {
315+ imessage : {
316+ dmPolicy : "allowlist" ,
317+ allowFrom : [ "+15550001111" ] ,
318+ sendReadReceipts : false ,
319+ } ,
320+ } ,
321+ messages : { inbound : { debounceMs : 0 } } ,
322+ session : { mainKey : "main" } ,
323+ } as never ,
324+ runtime : { error : vi . fn ( ) , exit : vi . fn ( ) , log : vi . fn ( ) } ,
325+ } ) ;
326+
327+ await vi . waitFor ( ( ) => {
328+ expect ( dispatchInboundMessageMock ) . toHaveBeenCalledTimes ( 1 ) ;
329+ } ) ;
330+ expect ( client . request ) . not . toHaveBeenCalledWith (
331+ "typing" ,
332+ expect . objectContaining ( { typing : true } ) ,
333+ expect . anything ( ) ,
334+ ) ;
335+ } ) ;
336+
259337 it ( "starts direct typing before dispatching the inbound turn" , async ( ) => {
260338 setCachedIMessagePrivateApiStatus ( "imsg" , {
261339 available : true ,
0 commit comments