-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Fix MCP elicitation deadlock and improve UX #6650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
ffc08f8
03c88a1
181e78a
43d21a5
9588aae
e84ff22
aef4031
7cc24f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,49 @@ | ||
| 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'; | ||
|
|
||
| // Timeout matches the server-side timeout in mcp_client.rs (300 seconds) | ||
| const ELICITATION_TIMEOUT_SECONDS = 300; | ||
|
|
||
| interface ElicitationRequestProps { | ||
| isCancelledMessage: boolean; | ||
| isClicked: boolean; | ||
| actionRequiredContent: ActionRequired & { type: 'actionRequired' }; | ||
| 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, | ||
| actionRequiredContent, | ||
| 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; | ||
|
Comment on lines
19
to
+32
|
||
|
|
||
| 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]); | ||
|
|
||
|
Comment on lines
+29
to
46
|
||
| if (actionRequiredContent.data.actionType !== 'elicitation') { | ||
| return null; | ||
|
|
@@ -57,17 +84,67 @@ 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 | ||
| schema={requested_schema as JsonSchema} | ||
| 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> | ||
| ); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.