@@ -224,9 +224,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
224224 }
225225
226226 const { id, method, params : rpcParams } = body
227- const apiKey =
228- request . headers . get ( 'X-API-Key' ) ||
229- request . headers . get ( 'Authorization' ) ?. replace ( 'Bearer ' , '' )
227+ // Only accept API keys from X-API-Key header to maintain proper auth boundaries
228+ const apiKey = request . headers . get ( 'X-API-Key' )
230229
231230 logger . info ( `A2A request: ${ method } for agent ${ agentId } ` )
232231
@@ -294,6 +293,9 @@ async function handleMessageSend(
294293 const taskId = message . taskId || generateTaskId ( )
295294 const contextId = message . contextId || uuidv4 ( )
296295
296+ // Distributed lock to prevent concurrent task processing
297+ // Note: When Redis is unavailable, acquireLock returns true (degraded mode).
298+ // In production, ensure Redis is available for proper distributed locking.
297299 const lockKey = `a2a:task:${ taskId } :lock`
298300 const lockValue = uuidv4 ( )
299301 const acquired = await acquireLock ( lockKey , lockValue , 60 )
@@ -427,9 +429,14 @@ async function handleMessageSend(
427429
428430 return NextResponse . json ( createResponse ( id , task ) )
429431 } catch ( error ) {
430- logger . error ( `Error executing workflow for task ${ taskId } :` , error )
432+ const isTimeout = error instanceof Error && error . name === 'TimeoutError'
433+ logger . error ( `Error executing workflow for task ${ taskId } :` , { error, isTimeout } )
431434
432- const errorMessage = error instanceof Error ? error . message : 'Workflow execution failed'
435+ const errorMessage = isTimeout
436+ ? `Workflow execution timed out after ${ A2A_DEFAULT_TIMEOUT } ms`
437+ : error instanceof Error
438+ ? error . message
439+ : 'Workflow execution failed'
433440
434441 await db
435442 . update ( a2aTask )
@@ -479,9 +486,14 @@ async function handleMessageStream(
479486 const contextId = message . contextId || uuidv4 ( )
480487 const taskId = message . taskId || generateTaskId ( )
481488
489+ // Distributed lock to prevent concurrent task processing
490+ // Note: When Redis is unavailable, acquireLock returns true (degraded mode).
491+ // Lock timeout: 5 minutes for streaming to handle long-running workflows.
492+ // If a streaming request fails without releasing the lock, subsequent requests
493+ // will be blocked until timeout. The lock is released in finally block below.
482494 const lockKey = `a2a:task:${ taskId } :lock`
483495 const lockValue = uuidv4 ( )
484- const acquired = await acquireLock ( lockKey , lockValue , 300 ) // 5 minute timeout for streaming
496+ const acquired = await acquireLock ( lockKey , lockValue , 300 )
485497
486498 if ( ! acquired ) {
487499 const encoder = new TextEncoder ( )
@@ -645,7 +657,11 @@ async function handleMessageStream(
645657 }
646658 }
647659
648- const messageContent = finalContent || accumulatedContent || 'Task completed'
660+ // Use finalContent if available and non-empty, otherwise fall back to accumulated content
661+ const messageContent =
662+ ( finalContent !== undefined && finalContent . length > 0
663+ ? finalContent
664+ : accumulatedContent ) || 'Task completed'
649665 const agentMessage = createAgentMessage ( messageContent )
650666 agentMessage . taskId = taskId
651667 if ( contextId ) agentMessage . contextId = contextId
@@ -718,7 +734,14 @@ async function handleMessageStream(
718734 } )
719735 }
720736 } catch ( error ) {
721- logger . error ( `Streaming error for task ${ taskId } :` , error )
737+ const isTimeout = error instanceof Error && error . name === 'TimeoutError'
738+ logger . error ( `Streaming error for task ${ taskId } :` , { error, isTimeout } )
739+
740+ const errorMessage = isTimeout
741+ ? `Workflow execution timed out after ${ A2A_DEFAULT_TIMEOUT } ms`
742+ : error instanceof Error
743+ ? error . message
744+ : 'Streaming failed'
722745
723746 await db
724747 . update ( a2aTask )
@@ -735,7 +758,7 @@ async function handleMessageStream(
735758
736759 sendEvent ( 'error' , {
737760 code : A2A_ERROR_CODES . INTERNAL_ERROR ,
738- message : error instanceof Error ? error . message : 'Streaming failed' ,
761+ message : errorMessage ,
739762 } )
740763 } finally {
741764 await releaseLock ( lockKey , lockValue )
@@ -862,7 +885,7 @@ async function handleTaskCancel(id: string | number, params: TaskIdParams): Prom
862885 * Handle tasks/resubscribe - Reconnect to SSE stream for an ongoing task
863886 */
864887async function handleTaskResubscribe (
865- _request : NextRequest ,
888+ request : NextRequest ,
866889 id : string | number ,
867890 params : TaskIdParams
868891) : Promise < NextResponse > {
@@ -896,10 +919,20 @@ async function handleTaskResubscribe(
896919 let isCancelled = false
897920 let pollTimeoutId : ReturnType < typeof setTimeout > | null = null
898921
922+ // Listen for client disconnection via request signal
923+ const abortSignal = request . signal
924+ abortSignal . addEventListener ( 'abort' , ( ) => {
925+ isCancelled = true
926+ if ( pollTimeoutId ) {
927+ clearTimeout ( pollTimeoutId )
928+ pollTimeoutId = null
929+ }
930+ } )
931+
899932 const stream = new ReadableStream ( {
900933 async start ( controller ) {
901934 const sendEvent = ( event : string , data : unknown ) : boolean => {
902- if ( isCancelled ) return false
935+ if ( isCancelled || abortSignal . aborted ) return false
903936 try {
904937 controller . enqueue ( encoder . encode ( `event: ${ event } \ndata: ${ JSON . stringify ( data ) } \n\n` ) )
905938 return true
@@ -935,7 +968,7 @@ async function handleTaskResubscribe(
935968
936969 let polls = 0
937970 const poll = async ( ) => {
938- if ( isCancelled ) {
971+ if ( isCancelled || abortSignal . aborted ) {
939972 cleanup ( )
940973 return
941974 }
0 commit comments