@@ -140,6 +140,7 @@ define(function (require, exports, module) {
140140 _nodeConnector . on ( "aiToolEdit" , _onToolEdit ) ;
141141 _nodeConnector . on ( "aiError" , _onError ) ;
142142 _nodeConnector . on ( "aiComplete" , _onComplete ) ;
143+ _nodeConnector . on ( "aiQuestion" , _onQuestion ) ;
143144
144145 // Check availability and render appropriate UI
145146 _checkAvailability ( ) ;
@@ -641,7 +642,8 @@ define(function (require, exports, module) {
641642 "mcp__phoenix-editor__controlEditor" : { icon : "fa-solid fa-code" , color : "#6bc76b" , label : Strings . AI_CHAT_TOOL_CONTROL_EDITOR } ,
642643 "mcp__phoenix-editor__resizeLivePreview" : { icon : "fa-solid fa-arrows-left-right" , color : "#66bb6a" , label : Strings . AI_CHAT_TOOL_RESIZE_PREVIEW } ,
643644 "mcp__phoenix-editor__wait" : { icon : "fa-solid fa-hourglass-half" , color : "#adb9bd" , label : Strings . AI_CHAT_TOOL_WAIT } ,
644- TodoWrite : { icon : "fa-solid fa-list-check" , color : "#66bb6a" , label : Strings . AI_CHAT_TOOL_TASKS }
645+ TodoWrite : { icon : "fa-solid fa-list-check" , color : "#66bb6a" , label : Strings . AI_CHAT_TOOL_TASKS } ,
646+ AskUserQuestion : { icon : "fa-solid fa-circle-question" , color : "#66bb6a" , label : Strings . AI_CHAT_TOOL_QUESTION }
645647 } ;
646648
647649 function _onProgress ( _event , data ) {
@@ -1134,6 +1136,148 @@ define(function (require, exports, module) {
11341136 } ) ;
11351137 }
11361138
1139+ /**
1140+ * Handle an AskUserQuestion tool call — render interactive question UI.
1141+ */
1142+ function _onQuestion ( _event , data ) {
1143+ const questions = data . questions || [ ] ;
1144+ if ( ! questions . length ) {
1145+ return ;
1146+ }
1147+
1148+ // Remove thinking indicator on first content
1149+ if ( ! _hasReceivedContent ) {
1150+ _hasReceivedContent = true ;
1151+ $messages . find ( ".ai-thinking" ) . remove ( ) ;
1152+ }
1153+
1154+ // Finalize current text segment so question appears after it
1155+ $messages . find ( ".ai-stream-target" ) . removeClass ( "ai-stream-target" ) ;
1156+ _segmentText = "" ;
1157+
1158+ const answers = { } ;
1159+ const totalQuestions = questions . length ;
1160+ let answeredCount = 0 ;
1161+
1162+ const $container = $ ( '<div class="ai-msg ai-msg-question"></div>' ) ;
1163+
1164+ questions . forEach ( function ( q ) {
1165+ const $qBlock = $ ( '<div class="ai-question-block"></div>' ) ;
1166+ const $qText = $ ( '<div class="ai-question-text"></div>' ) ;
1167+ $qText . text ( q . question ) ;
1168+ $qBlock . append ( $qText ) ;
1169+
1170+ const $options = $ ( '<div class="ai-question-options"></div>' ) ;
1171+
1172+ q . options . forEach ( function ( opt ) {
1173+ const $opt = $ ( '<button class="ai-question-option"></button>' ) ;
1174+ const $label = $ ( '<span class="ai-question-option-label"></span>' ) ;
1175+ $label . text ( opt . label ) ;
1176+ $opt . append ( $label ) ;
1177+ if ( opt . description ) {
1178+ const $desc = $ ( '<span class="ai-question-option-desc"></span>' ) ;
1179+ $desc . text ( opt . description ) ;
1180+ $opt . append ( $desc ) ;
1181+ }
1182+
1183+ $opt . on ( "click" , function ( ) {
1184+ if ( $qBlock . data ( "answered" ) ) {
1185+ return ;
1186+ }
1187+ if ( q . multiSelect ) {
1188+ $opt . toggleClass ( "selected" ) ;
1189+ } else {
1190+ // Single select — answer immediately
1191+ $qBlock . data ( "answered" , true ) ;
1192+ $options . find ( ".ai-question-option" ) . prop ( "disabled" , true ) ;
1193+ $opt . addClass ( "selected" ) ;
1194+ $qBlock . find ( ".ai-question-other" ) . hide ( ) ;
1195+ answers [ q . question ] = opt . label ;
1196+ answeredCount ++ ;
1197+ if ( answeredCount >= totalQuestions ) {
1198+ _sendQuestionAnswers ( answers ) ;
1199+ }
1200+ }
1201+ } ) ;
1202+ $options . append ( $opt ) ;
1203+ } ) ;
1204+
1205+ // Multi-select submit button
1206+ if ( q . multiSelect ) {
1207+ const $submit = $ ( '<button class="ai-question-submit">' +
1208+ '<i class="fa-solid fa-paper-plane"></i></button>' ) ;
1209+ $submit . on ( "click" , function ( ) {
1210+ if ( $qBlock . data ( "answered" ) ) {
1211+ return ;
1212+ }
1213+ const selected = [ ] ;
1214+ $options . find ( ".ai-question-option.selected" ) . each ( function ( ) {
1215+ selected . push ( $ ( this ) . find ( ".ai-question-option-label" ) . text ( ) ) ;
1216+ } ) ;
1217+ if ( ! selected . length ) {
1218+ return ;
1219+ }
1220+ $qBlock . data ( "answered" , true ) ;
1221+ $options . find ( ".ai-question-option" ) . prop ( "disabled" , true ) ;
1222+ $submit . prop ( "disabled" , true ) ;
1223+ $qBlock . find ( ".ai-question-other" ) . hide ( ) ;
1224+ answers [ q . question ] = selected . join ( ", " ) ;
1225+ answeredCount ++ ;
1226+ if ( answeredCount >= totalQuestions ) {
1227+ _sendQuestionAnswers ( answers ) ;
1228+ }
1229+ } ) ;
1230+ $options . append ( $submit ) ;
1231+ }
1232+
1233+ $qBlock . append ( $options ) ;
1234+
1235+ // "Other" free-text input
1236+ const $other = $ ( '<div class="ai-question-other"></div>' ) ;
1237+ const $input = $ ( '<input type="text" class="ai-question-other-input" ' +
1238+ 'placeholder="' + Strings . AI_CHAT_QUESTION_OTHER + '">' ) ;
1239+ const $sendOther = $ ( '<button class="ai-question-other-submit">' +
1240+ '<i class="fa-solid fa-paper-plane"></i></button>' ) ;
1241+ function submitOther ( ) {
1242+ const val = $input . val ( ) . trim ( ) ;
1243+ if ( ! val || $qBlock . data ( "answered" ) ) {
1244+ return ;
1245+ }
1246+ $qBlock . data ( "answered" , true ) ;
1247+ $options . find ( ".ai-question-option" ) . prop ( "disabled" , true ) ;
1248+ $input . prop ( "disabled" , true ) ;
1249+ $sendOther . prop ( "disabled" , true ) ;
1250+ answers [ q . question ] = val ;
1251+ answeredCount ++ ;
1252+ if ( answeredCount >= totalQuestions ) {
1253+ _sendQuestionAnswers ( answers ) ;
1254+ }
1255+ }
1256+ $sendOther . on ( "click" , submitOther ) ;
1257+ $input . on ( "keydown" , function ( e ) {
1258+ if ( e . key === "Enter" ) {
1259+ submitOther ( ) ;
1260+ }
1261+ } ) ;
1262+ $other . append ( $input ) . append ( $sendOther ) ;
1263+ $qBlock . append ( $other ) ;
1264+
1265+ $container . append ( $qBlock ) ;
1266+ } ) ;
1267+
1268+ $messages . append ( $container ) ;
1269+ _scrollToBottom ( ) ;
1270+ }
1271+
1272+ /**
1273+ * Send collected question answers to the node side.
1274+ */
1275+ function _sendQuestionAnswers ( answers ) {
1276+ _nodeConnector . execPeer ( "answerQuestion" , { answers : answers } ) . catch ( function ( err ) {
1277+ console . warn ( "[AI UI] Failed to send question answer:" , err . message ) ;
1278+ } ) ;
1279+ }
1280+
11371281 // --- DOM helpers ---
11381282
11391283 function _appendUserMessage ( text ) {
@@ -1523,6 +1667,13 @@ define(function (require, exports, module) {
15231667 summary : StringUtils . format ( Strings . AI_CHAT_TOOL_WAITING , input . seconds || "?" ) ,
15241668 lines : [ ]
15251669 } ;
1670+ case "AskUserQuestion" : {
1671+ const qs = input . questions || [ ] ;
1672+ return {
1673+ summary : Strings . AI_CHAT_TOOL_QUESTION ,
1674+ lines : qs . map ( function ( q ) { return q . question ; } )
1675+ } ;
1676+ }
15261677 case "TodoWrite" : {
15271678 const todos = input . todos || [ ] ;
15281679 const completed = todos . filter ( function ( t ) { return t . status === "completed" ; } ) . length ;
0 commit comments