Fix MCP elicitation deadlock and improve UX (#6650)

This commit is contained in:
Rizel Scarlett
2026-01-23 14:58:57 -05:00
committed by GitHub
parent 8233f0afac
commit c1a916efd9
2 changed files with 121 additions and 33 deletions
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import { ActionRequired } from '../api';
import JsonSchemaForm from './ui/JsonSchemaForm';
import type { JsonSchema } from './ui/JsonSchemaForm';
const ELICITATION_TIMEOUT_SECONDS = 300;
interface ElicitationRequestProps {
isCancelledMessage: boolean;
isClicked: boolean;
@@ -10,6 +12,12 @@ interface ElicitationRequestProps {
onSubmit: (elicitationId: string, userData: Record<string, unknown>) => void;
}
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
export default function ElicitationRequest({
isCancelledMessage,
isClicked,
@@ -17,6 +25,24 @@ export default function ElicitationRequest({
onSubmit,
}: ElicitationRequestProps) {
const [submitted, setSubmitted] = useState(isClicked);
const [timeRemaining, setTimeRemaining] = useState(ELICITATION_TIMEOUT_SECONDS);
const startTimeRef = useRef(Date.now());
useEffect(() => {
if (submitted || isCancelledMessage || isClicked) return;
const interval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTimeRef.current) / 1000);
const remaining = Math.max(0, ELICITATION_TIMEOUT_SECONDS - elapsed);
setTimeRemaining(remaining);
if (remaining === 0) {
clearInterval(interval);
}
}, 1000);
return () => clearInterval(interval);
}, [submitted, isCancelledMessage, isClicked]);
if (actionRequiredContent.data.actionType !== 'elicitation') {
return null;
@@ -57,10 +83,39 @@ export default function ElicitationRequest({
);
}
const isUrgent = timeRemaining <= 60;
const isExpired = timeRemaining === 0;
if (isExpired) {
return (
<div className="goose-message-content bg-background-muted rounded-2xl px-4 py-2 text-textStandard">
<div className="flex items-center gap-2 text-textSubtle">
<svg
className="w-5 h-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>This request has expired. The extension will need to ask again.</span>
</div>
</div>
);
}
return (
<div className="flex flex-col">
<div className="goose-message-content bg-background-muted rounded-2xl rounded-b-none px-4 py-2 text-textStandard">
{message || 'Goose needs some information from you.'}
<div className="flex justify-between items-start gap-4">
<span>{message || 'Goose needs some information from you.'}</span>
</div>
</div>
<div className="goose-message-content bg-background-default border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 py-3">
<JsonSchemaForm
@@ -68,6 +123,27 @@ export default function ElicitationRequest({
onSubmit={handleSubmit}
submitLabel="Submit"
/>
<div
className={`mt-3 pt-3 border-t border-borderSubtle flex items-center gap-2 text-sm ${isUrgent ? 'text-red-500' : 'text-textSubtle'}`}
>
<svg
className="w-4 h-4 animate-pulse"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
Waiting for your response ({formatTime(timeRemaining)} remaining)
</span>
</div>
</div>
</div>
);