@@ -140,6 +140,77 @@ function storeLatestResponse(
140140 ) ;
141141}
142142
143+ /**
144+ * Store incoming request for Claude to pick up
145+ * Used when a WhatsApp/SMS message arrives without a pending prompt
146+ */
147+ function storeIncomingRequest ( from : string , message : string ) : void {
148+ ensureSecureDir ( join ( homedir ( ) , '.stackmemory' ) ) ;
149+ const requestPath = join (
150+ homedir ( ) ,
151+ '.stackmemory' ,
152+ 'sms-incoming-request.json'
153+ ) ;
154+ writeFileSecure (
155+ requestPath ,
156+ JSON . stringify ( {
157+ from,
158+ message,
159+ timestamp : new Date ( ) . toISOString ( ) ,
160+ processed : false ,
161+ } )
162+ ) ;
163+ }
164+
165+ /**
166+ * Get pending incoming request (if any)
167+ */
168+ export function getIncomingRequest ( ) : {
169+ from : string ;
170+ message : string ;
171+ timestamp : string ;
172+ processed : boolean ;
173+ } | null {
174+ const requestPath = join (
175+ homedir ( ) ,
176+ '.stackmemory' ,
177+ 'sms-incoming-request.json'
178+ ) ;
179+ if ( ! existsSync ( requestPath ) ) {
180+ return null ;
181+ }
182+ try {
183+ const data = JSON . parse ( readFileSync ( requestPath , 'utf-8' ) ) ;
184+ if ( data . processed ) {
185+ return null ;
186+ }
187+ return data ;
188+ } catch {
189+ return null ;
190+ }
191+ }
192+
193+ /**
194+ * Mark incoming request as processed
195+ */
196+ export function markRequestProcessed ( ) : void {
197+ const requestPath = join (
198+ homedir ( ) ,
199+ '.stackmemory' ,
200+ 'sms-incoming-request.json'
201+ ) ;
202+ if ( ! existsSync ( requestPath ) ) {
203+ return ;
204+ }
205+ try {
206+ const data = JSON . parse ( readFileSync ( requestPath , 'utf-8' ) ) ;
207+ data . processed = true ;
208+ writeFileSecure ( requestPath , JSON . stringify ( data ) ) ;
209+ } catch {
210+ // Ignore errors
211+ }
212+ }
213+
143214export async function handleSMSWebhook ( payload : TwilioWebhookPayload ) : Promise < {
144215 response : string ;
145216 action ?: string ;
@@ -168,7 +239,12 @@ export async function handleSMSWebhook(payload: TwilioWebhookPayload): Promise<{
168239 response : `Invalid response. Expected: ${ result . prompt . options . map ( ( o ) => o . key ) . join ( ', ' ) } ` ,
169240 } ;
170241 }
171- return { response : 'No pending prompt found.' } ;
242+ // No pending prompt - store as new incoming request for Claude
243+ storeIncomingRequest ( From , Body ) ;
244+ console . log (
245+ `[sms-webhook] Stored new request from ${ From } : ${ Body . substring ( 0 , 50 ) } ...`
246+ ) ;
247+ return { response : 'Got it! Your request has been queued.' } ;
172248 }
173249
174250 // Store response for Claude hook
@@ -450,6 +526,22 @@ export function startWebhookServer(port: number = 3456): void {
450526 return ;
451527 }
452528
529+ // Get pending incoming request endpoint
530+ if ( url . pathname === '/request' && req . method === 'GET' ) {
531+ const request = getIncomingRequest ( ) ;
532+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
533+ res . end ( JSON . stringify ( { request } ) ) ;
534+ return ;
535+ }
536+
537+ // Mark request as processed endpoint
538+ if ( url . pathname === '/request/ack' && req . method === 'POST' ) {
539+ markRequestProcessed ( ) ;
540+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
541+ res . end ( JSON . stringify ( { success : true } ) ) ;
542+ return ;
543+ }
544+
453545 // Send outgoing notification endpoint
454546 if ( url . pathname === '/send' && req . method === 'POST' ) {
455547 let body = '' ;
0 commit comments