@@ -179,6 +179,10 @@ interface AgentEvt {
179179 requestId ?: string ;
180180 toolName ?: string ;
181181 reason ?: string ;
182+ // ask_user fields (AskUserQuestion tool)
183+ question ?: string ;
184+ options ?: Array < { label : string ; description : string } > ;
185+ multiSelect ?: boolean ;
182186 // tool_use carries id; we use it on tool_result to attach output to the right card
183187 id ?: string ;
184188}
@@ -189,6 +193,13 @@ interface PendingApproval {
189193 reason : string ;
190194}
191195
196+ interface PendingQuestion {
197+ requestId : string ;
198+ question : string ;
199+ options : Array < { label : string ; description : string } > ;
200+ multiSelect ?: boolean ;
201+ }
202+
192203// ─── Component ────────────────────────────────────────────────────────
193204
194205export function ReplScreen ( {
@@ -217,6 +228,7 @@ export function ReplScreen({
217228 const [ busy , setBusy ] = useState ( false ) ;
218229 const [ activeTurnId , setActiveTurnId ] = useState < string | null > ( null ) ;
219230 const [ pendingApproval , setPendingApproval ] = useState < PendingApproval | null > ( null ) ;
231+ const [ pendingQuestion , setPendingQuestion ] = useState < PendingQuestion | null > ( null ) ;
220232 // 'high' (6k output) by default — 'medium' (3k) truncates multi-file writes.
221233 // Overridden by a persisted effortLevel in settings on mount.
222234 const [ effort , setEffort ] = useState < Effort > ( 'high' ) ;
@@ -279,6 +291,8 @@ export function ReplScreen({
279291 if ( e . kind === 'turn_done' ) {
280292 setBusy ( false ) ;
281293 setActiveTurnId ( null ) ;
294+ setPendingApproval ( null ) ;
295+ setPendingQuestion ( null ) ;
282296 setMessages ( ( m ) => finalizeStreaming ( m ) ) ;
283297 onTurnComplete ?.( ) ;
284298 return ;
@@ -351,6 +365,16 @@ export function ReplScreen({
351365 } ) ;
352366 }
353367 break ;
368+ case 'ask_user' :
369+ if ( e . requestId && e . question ) {
370+ setPendingQuestion ( {
371+ requestId : e . requestId ,
372+ question : e . question ,
373+ options : e . options ?? [ ] ,
374+ multiSelect : e . multiSelect ,
375+ } ) ;
376+ }
377+ break ;
354378 }
355379 } ) ;
356380 return ( ) => off ( ) ;
@@ -491,11 +515,20 @@ export function ReplScreen({
491515 await window . deepcode . agent . approve ( { requestId : req . requestId , decision } ) ;
492516 }
493517
518+ // ── AskUserQuestion answer ──
519+ async function handleAnswer ( answer : string ) : Promise < void > {
520+ if ( ! pendingQuestion ) return ;
521+ const req = pendingQuestion ;
522+ setPendingQuestion ( null ) ;
523+ setMessages ( ( m ) => [ ...m , { role : 'system' , text : `❯ ${ req . question } → ${ answer } ` } ] ) ;
524+ await window . deepcode . agent . answer ( { requestId : req . requestId , answer } ) ;
525+ }
526+
494527 // ── Send ──
495528 async function handleSubmit ( e : React . FormEvent ) : Promise < void > {
496529 e . preventDefault ( ) ;
497530 const text = input . trim ( ) ;
498- if ( ! text || busy || pendingApproval ) return ;
531+ if ( ! text || busy || pendingApproval || pendingQuestion ) return ;
499532 setInput ( '' ) ;
500533 setMessages ( ( m ) => [ ...m , { role : 'user' , text } ] ) ;
501534 setBusy ( true ) ;
@@ -530,7 +563,7 @@ export function ReplScreen({
530563 // Lock all the toolbar controls (mode / model / effort) once a turn
531564 // is in flight or pending approval — changing them mid-turn would
532565 // contradict the system prompt already sent.
533- const controlsLocked = busy || pendingApproval !== null ;
566+ const controlsLocked = busy || pendingApproval !== null || pendingQuestion !== null ;
534567
535568 // Only the last assistant turn is "active" — its cursor blinks while the rest
536569 // stay static. Guards against a second cursor if a turn was left streaming.
@@ -577,7 +610,7 @@ export function ReplScreen({
577610 renderMessage ( m , i , pendingApproval , handleApproval , i === activeAssistantIdx ) ,
578611 ) }
579612
580- { busy && ! pendingApproval && (
613+ { busy && ! pendingApproval && ! pendingQuestion && (
581614 < div className = "msg assistant" >
582615 < div className = "avatar" > DC</ div >
583616 < div className = "body" >
@@ -588,6 +621,31 @@ export function ReplScreen({
588621 </ div >
589622 </ div >
590623 ) }
624+
625+ { pendingQuestion && (
626+ < div className = "msg assistant" >
627+ < div className = "avatar" > DC</ div >
628+ < div className = "body" >
629+ < div className = "author" > DeepCode · needs your input</ div >
630+ < div className = "content" >
631+ < div style = { { marginBottom : 8 } } > { pendingQuestion . question } </ div >
632+ < div className = "approval-row" style = { { flexWrap : 'wrap' } } >
633+ { pendingQuestion . options . map ( ( o ) => (
634+ < button
635+ key = { o . label }
636+ type = "button"
637+ className = "btn btn-secondary"
638+ title = { o . description }
639+ onClick = { ( ) => void handleAnswer ( o . label ) }
640+ >
641+ { o . label }
642+ </ button >
643+ ) ) }
644+ </ div >
645+ </ div >
646+ </ div >
647+ </ div >
648+ ) }
591649 </ div >
592650
593651 < div className = "composer" >
@@ -599,13 +657,15 @@ export function ReplScreen({
599657 onChange = { ( e ) => setInput ( e . target . value ) }
600658 onKeyDown = { handleKeyDown }
601659 placeholder = {
602- pendingApproval
603- ? 'Approve or reject the tool call above to continue…'
604- : busy
605- ? 'Agent is responding…'
606- : '问点什么… @ 引用文件 · / 命令 · # 写入 DEEPCODE.md'
660+ pendingQuestion
661+ ? 'Pick an option above to continue…'
662+ : pendingApproval
663+ ? 'Approve or reject the tool call above to continue…'
664+ : busy
665+ ? 'Agent is responding…'
666+ : '问点什么… @ 引用文件 · / 命令 · # 写入 DEEPCODE.md'
607667 }
608- disabled = { busy || pendingApproval !== null }
668+ disabled = { busy || pendingApproval !== null || pendingQuestion !== null }
609669 rows = { Math . min ( 6 , Math . max ( 1 , input . split ( '\n' ) . length ) ) }
610670 />
611671 < div className = "toolbar" >
0 commit comments