Skip to content

Commit d66a4f0

Browse files
committed
fix: terminal response timeout — extend chat.send to 5min, resilient stream handling
Root cause: gateway-context had a 30-second timeout on ALL sendRequest calls. Agent responses taking >30s would reject the promise, terminal showed 'Send failed', and any streaming events arriving after were orphaned. Changes: - gateway-context: chat.send timeout extended to 5 minutes (300s) - terminal: don't clear sending state if streaming already started via events - terminal: 3-minute safety timeout as backstop (was 2min, separate from RPC timeout) - terminal: status-agnostic response handling (checks for inline reply text first) - Added debug logging ([GW-Terminal]) for diagnosing event flow
1 parent 62e7bb7 commit d66a4f0

2 files changed

Lines changed: 44 additions & 24 deletions

File tree

components/gateway-terminal.tsx

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,14 @@ export function GatewayTerminal() {
448448
(typeof p.session === 'object' && p.session !== null
449449
? (p.session as Record<string, unknown>).key
450450
: undefined)) as string | undefined
451-
if (eventSessionKey && eventSessionKey !== 'main') return
451+
452+
// Debug: log all chat events the terminal sees
453+
console.log('[GW-Terminal] chat event:', { state, sessionKey: eventSessionKey, keys: Object.keys(p).join(',') })
454+
455+
if (eventSessionKey && eventSessionKey !== 'main') {
456+
console.log('[GW-Terminal] skipping event — session mismatch:', eventSessionKey)
457+
return
458+
}
452459

453460
if (state === 'delta') {
454461
const text = extractEventText(p)
@@ -546,6 +553,18 @@ export function GatewayTerminal() {
546553
setSending(true)
547554
streamBuf.current = ''
548555
streamId.current = null
556+
557+
// Safety timeout — if nothing arrives via events within 3 min, stop waiting
558+
const safetyTimer = setTimeout(() => {
559+
setSending((prev) => {
560+
if (prev) {
561+
addEntry('system', '⏱ Response timed out — no reply received')
562+
return false
563+
}
564+
return prev
565+
})
566+
}, 180000)
567+
549568
try {
550569
const resp = (await sendRequest('chat.send', {
551570
sessionKey: 'main',
@@ -554,37 +573,36 @@ export function GatewayTerminal() {
554573
})) as Record<string, unknown> | undefined
555574

556575
const respStatus = resp?.status as string | undefined
576+
console.log('[GW-Terminal] chat.send response:', { status: respStatus, keys: resp ? Object.keys(resp).join(',') : 'null' })
557577

558-
// Streaming/async — response will arrive via onEvent('chat')
559-
if (respStatus === 'started' || respStatus === 'in_flight' || respStatus === 'streaming') {
560-
// setSending stays true; events will clear it on 'final'
561-
setTimeout(
562-
() =>
563-
setSending((prev) => {
564-
if (prev) {
565-
addEntry('system', '⏱ Response timed out')
566-
return false
567-
}
568-
return prev
569-
}),
570-
120000, // 2 min timeout for long agent runs
571-
)
578+
// Check if the response contains an inline reply (synchronous path)
579+
const inlineReply = extractEventText(resp)
580+
if (inlineReply && !isNoReply(inlineReply)) {
581+
// If we haven't already rendered this via streaming events, add it
582+
if (!streamId.current) {
583+
addEntry('response', inlineReply)
584+
}
585+
clearTimeout(safetyTimer)
586+
setSending(false)
572587
return
573588
}
574589

575-
// Synchronous reply
576-
if (respStatus === 'ok' || !respStatus) {
577-
const reply = extractEventText(resp)
578-
if (reply && !isNoReply(reply)) {
579-
addEntry('response', reply)
580-
}
590+
if (isNoReply(inlineReply)) {
591+
clearTimeout(safetyTimer)
581592
setSending(false)
582593
return
583594
}
584595

585-
// Unknown status — still wait for events
586-
setSending(false)
596+
// No inline reply — response will arrive via chat events (streaming)
597+
// setSending stays true; the event listener's 'final' handler clears it
598+
// Safety timer is still running as a backstop
587599
} catch (e) {
600+
// If streaming already started via events, don't kill it
601+
if (streamId.current) {
602+
console.log('[GW-Terminal] sendRequest errored but stream in progress, continuing')
603+
return
604+
}
605+
clearTimeout(safetyTimer)
588606
addEntry('error', e instanceof Error ? e.message : 'Send failed')
589607
setSending(false)
590608
}

context/gateway-context.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,12 @@ export function GatewayProvider({ children }: { children: React.ReactNode }) {
421421
}
422422

423423
const req = makeRequest(method, params)
424+
// Chat requests can take minutes (agent runs); use longer timeout
425+
const timeoutMs = method === 'chat.send' ? 300000 : 30000
424426
const timer = setTimeout(() => {
425427
pendingRef.current.delete(req.id)
426428
reject(new Error(`Request '${method}' timed out`))
427-
}, 30000)
429+
}, timeoutMs)
428430
pendingRef.current.set(req.id, { resolve, reject, timer })
429431
wsRef.current.send(JSON.stringify(req))
430432
})

0 commit comments

Comments
 (0)