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
// (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
? {
type: 'control_response',
response: {
subtype: 'success',
request_id: requestId,
response: { behavior: 'allow' },
response: allowResponse,
},
}
: {

View file

@ -170,6 +170,12 @@ export const ToolApprovalSheet: React.FC = () => {
setDisabled(true);
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),
// re-enable the button so the user isn't stuck forever.
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.');
}, RESPOND_TIMEOUT_MS);
respondToToolApproval(current.teamName, current.runId, current.requestId, allow)
respondToToolApproval(
current.teamName,
current.runId,
current.requestId,
allow,
answersMessage
)
.then(() => {
clearTimeout(safetyTimer);
// Small delay before re-enabling to prevent accidental double-clicks
@ -190,7 +202,7 @@ export const ToolApprovalSheet: React.FC = () => {
setDisabled(false);
});
},
[current, disabled, respondToToolApproval]
[current, disabled, respondToToolApproval, selectedOptions]
);
const isAskQuestion = current?.toolName === 'AskUserQuestion';
@ -213,6 +225,7 @@ export const ToolApprovalSheet: React.FC = () => {
const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
if (e.key === 'Enter') {
if (isAskQuestion && !hasSelection) return;
e.preventDefault();
handleRespond(true);
} else if (e.key === 'Escape') {
@ -223,7 +236,7 @@ export const ToolApprovalSheet: React.FC = () => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleRespond]);
}, [handleRespond, isAskQuestion, hasSelection]);
// Resolve teammate color for MemberBadge (when source !== 'lead')
const sourceColor = useMemo(() => {