@@ -20,6 +20,7 @@ import {
2020export interface ReplyResult {
2121 success : boolean ;
2222 draftId ?: string ;
23+ error ?: string ;
2324}
2425
2526/**
@@ -32,39 +33,71 @@ async function completeDraft(
3233) : Promise < ReplyResult > {
3334 if ( send ) {
3435 const sent = await sendDraft ( conn ) ;
35- return { success : sent } ;
36+ if ( ! sent ) {
37+ return { success : false , error : "Failed to send draft" } ;
38+ }
39+ return { success : true } ;
3640 }
3741
3842 const saved = await saveDraft ( conn ) ;
39- return { success : saved , draftId : draftKey } ;
43+ if ( ! saved ) {
44+ return { success : false , error : "Failed to save draft" } ;
45+ }
46+ return { success : true , draftId : draftKey } ;
47+ }
48+
49+ /**
50+ * Retry a function with exponential backoff
51+ */
52+ async function withRetry < T > (
53+ fn : ( ) => Promise < T | null > ,
54+ maxRetries : number = 3 ,
55+ baseDelay : number = 500
56+ ) : Promise < T | null > {
57+ for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
58+ const result = await fn ( ) ;
59+ if ( result !== null ) {
60+ return result ;
61+ }
62+ if ( attempt < maxRetries - 1 ) {
63+ const delay = baseDelay * Math . pow ( 2 , attempt ) ;
64+ await new Promise ( r => setTimeout ( r , delay ) ) ;
65+ }
66+ }
67+ return null ;
4068}
4169
4270/**
4371 * Reply to a thread
4472 *
4573 * Uses Superhuman's native REPLY_POP_OUT command which properly sets up
4674 * threading (threadId, inReplyTo, references), recipients, and subject.
75+ * Includes retry logic for transient UI state failures.
4776 *
4877 * @param conn - The Superhuman connection
4978 * @param threadId - The thread ID to reply to
5079 * @param body - The reply body text
5180 * @param send - If true, send immediately; if false, save as draft
52- * @returns Result with success status and optional draft ID
81+ * @returns Result with success status, optional draft ID, and error message if failed
5382 */
5483export async function replyToThread (
5584 conn : SuperhumanConnection ,
5685 threadId : string ,
5786 body : string ,
5887 send : boolean = false
5988) : Promise < ReplyResult > {
60- const draftKey = await openReplyCompose ( conn , threadId ) ;
89+ // Retry opening compose in case of transient UI state issues
90+ const draftKey = await withRetry ( ( ) => openReplyCompose ( conn , threadId ) , 3 , 500 ) ;
6191 if ( ! draftKey ) {
62- return { success : false } ;
92+ return {
93+ success : false ,
94+ error : "Failed to open reply compose (UI may be blocked by existing compose window or overlay)"
95+ } ;
6396 }
6497
6598 const bodySet = await setBody ( conn , textToHtml ( body ) ) ;
6699 if ( ! bodySet ) {
67- return { success : false } ;
100+ return { success : false , error : "Failed to set reply body" } ;
68101 }
69102
70103 return completeDraft ( conn , draftKey , send ) ;
@@ -75,28 +108,32 @@ export async function replyToThread(
75108 *
76109 * Uses Superhuman's native REPLY_ALL_POP_OUT command which properly sets up
77110 * threading (threadId, inReplyTo, references), all recipients (To and Cc),
78- * and subject automatically.
111+ * and subject automatically. Includes retry logic for transient UI state failures.
79112 *
80113 * @param conn - The Superhuman connection
81114 * @param threadId - The thread ID to reply to
82115 * @param body - The reply body text
83116 * @param send - If true, send immediately; if false, save as draft
84- * @returns Result with success status and optional draft ID
117+ * @returns Result with success status, optional draft ID, and error message if failed
85118 */
86119export async function replyAllToThread (
87120 conn : SuperhumanConnection ,
88121 threadId : string ,
89122 body : string ,
90123 send : boolean = false
91124) : Promise < ReplyResult > {
92- const draftKey = await openReplyAllCompose ( conn , threadId ) ;
125+ // Retry opening compose in case of transient UI state issues
126+ const draftKey = await withRetry ( ( ) => openReplyAllCompose ( conn , threadId ) , 3 , 500 ) ;
93127 if ( ! draftKey ) {
94- return { success : false } ;
128+ return {
129+ success : false ,
130+ error : "Failed to open reply-all compose (UI may be blocked by existing compose window or overlay)"
131+ } ;
95132 }
96133
97134 const bodySet = await setBody ( conn , textToHtml ( body ) ) ;
98135 if ( ! bodySet ) {
99- return { success : false } ;
136+ return { success : false , error : "Failed to set reply body" } ;
100137 }
101138
102139 return completeDraft ( conn , draftKey , send ) ;
@@ -107,13 +144,14 @@ export async function replyAllToThread(
107144 *
108145 * Uses Superhuman's native FORWARD_POP_OUT command which properly sets up
109146 * the forwarded message content, subject, and formatting.
147+ * Includes retry logic for transient UI state failures.
110148 *
111149 * @param conn - The Superhuman connection
112150 * @param threadId - The thread ID to forward
113151 * @param toEmail - The email address to forward to
114152 * @param body - The message body to include before the forwarded content
115153 * @param send - If true, send immediately; if false, save as draft
116- * @returns Result with success status and optional draft ID
154+ * @returns Result with success status, optional draft ID, and error message if failed
117155 */
118156export async function forwardThread (
119157 conn : SuperhumanConnection ,
@@ -122,20 +160,24 @@ export async function forwardThread(
122160 body : string ,
123161 send : boolean = false
124162) : Promise < ReplyResult > {
125- const draftKey = await openForwardCompose ( conn , threadId ) ;
163+ // Retry opening compose in case of transient UI state issues
164+ const draftKey = await withRetry ( ( ) => openForwardCompose ( conn , threadId ) , 3 , 500 ) ;
126165 if ( ! draftKey ) {
127- return { success : false } ;
166+ return {
167+ success : false ,
168+ error : "Failed to open forward compose (UI may be blocked by existing compose window or overlay)"
169+ } ;
128170 }
129171
130172 const recipientAdded = await addRecipient ( conn , toEmail ) ;
131173 if ( ! recipientAdded ) {
132- return { success : false } ;
174+ return { success : false , error : "Failed to add forward recipient" } ;
133175 }
134176
135177 if ( body ) {
136178 const bodySet = await setBody ( conn , textToHtml ( body ) ) ;
137179 if ( ! bodySet ) {
138- return { success : false } ;
180+ return { success : false , error : "Failed to set forward body" } ;
139181 }
140182 }
141183
0 commit comments