fix(team): deliver AskUserQuestion answers via updatedInput + fix Enter bypass

- Enter key now respects isAskQuestion selection requirement
- Selected option labels sent as message to respondToToolApproval
- control_response includes updatedInput with answers for AskUserQuestion
- useCallback/useEffect deps updated for selectedOptions and hasSelection
This commit is contained in:
iliya 2026-03-28 16:54:22 +02:00
parent 1e80f3db52
commit 75a36b5cd2
2 changed files with 31 additions and 4 deletions

View file

@ -6042,13 +6042,27 @@ export class TeamProvisioningService {
// IMPORTANT: request_id is NESTED inside response, NOT top-level // IMPORTANT: request_id is NESTED inside response, NOT top-level
// (asymmetry with control_request — confirmed by Python SDK, Elixir SDK and issue #29991) // (asymmetry with control_request — confirmed by Python SDK, Elixir SDK and issue #29991)
const allowResponse: Record<string, unknown> = { behavior: 'allow' };
// For AskUserQuestion: pass user's answer via updatedInput so the CLI
// can deliver it without re-prompting. Format follows --permission-prompt-tool spec.
if (allow && message) {
const pending = run.pendingApprovals.get(requestId);
if (pending?.toolName === 'AskUserQuestion') {
const questions = (pending.toolInput.questions as { question?: string }[]) ?? [];
const answers: Record<string, string> = {};
for (const q of questions) {
if (q.question) answers[q.question] = message;
}
allowResponse.updatedInput = { ...pending.toolInput, answers };
}
}
const response = allow const response = allow
? { ? {
type: 'control_response', type: 'control_response',
response: { response: {
subtype: 'success', subtype: 'success',
request_id: requestId, request_id: requestId,
response: { behavior: 'allow' }, response: allowResponse,
}, },
} }
: { : {

View file

@ -170,6 +170,12 @@ export const ToolApprovalSheet: React.FC = () => {
setDisabled(true); setDisabled(true);
setError(null); setError(null);
// For AskUserQuestion, build answers from selected options
const answersMessage =
allow && current.toolName === 'AskUserQuestion' && selectedOptions.size > 0
? Array.from(selectedOptions).join(', ')
: undefined;
// Safety timeout — if IPC hangs (e.g. stdin.write callback never fires), // Safety timeout — if IPC hangs (e.g. stdin.write callback never fires),
// re-enable the button so the user isn't stuck forever. // re-enable the button so the user isn't stuck forever.
const safetyTimer = setTimeout(() => { const safetyTimer = setTimeout(() => {
@ -177,7 +183,13 @@ export const ToolApprovalSheet: React.FC = () => {
setError('Response timed out — process may be unresponsive. Try again or stop the team.'); setError('Response timed out — process may be unresponsive. Try again or stop the team.');
}, RESPOND_TIMEOUT_MS); }, RESPOND_TIMEOUT_MS);
respondToToolApproval(current.teamName, current.runId, current.requestId, allow) respondToToolApproval(
current.teamName,
current.runId,
current.requestId,
allow,
answersMessage
)
.then(() => { .then(() => {
clearTimeout(safetyTimer); clearTimeout(safetyTimer);
// Small delay before re-enabling to prevent accidental double-clicks // Small delay before re-enabling to prevent accidental double-clicks
@ -190,7 +202,7 @@ export const ToolApprovalSheet: React.FC = () => {
setDisabled(false); setDisabled(false);
}); });
}, },
[current, disabled, respondToToolApproval] [current, disabled, respondToToolApproval, selectedOptions]
); );
const isAskQuestion = current?.toolName === 'AskUserQuestion'; const isAskQuestion = current?.toolName === 'AskUserQuestion';
@ -213,6 +225,7 @@ export const ToolApprovalSheet: React.FC = () => {
const tag = document.activeElement?.tagName; const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
if (e.key === 'Enter') { if (e.key === 'Enter') {
if (isAskQuestion && !hasSelection) return;
e.preventDefault(); e.preventDefault();
handleRespond(true); handleRespond(true);
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
@ -223,7 +236,7 @@ export const ToolApprovalSheet: React.FC = () => {
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleRespond]); }, [handleRespond, isAskQuestion, hasSelection]);
// Resolve teammate color for MemberBadge (when source !== 'lead') // Resolve teammate color for MemberBadge (when source !== 'lead')
const sourceColor = useMemo(() => { const sourceColor = useMemo(() => {