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:
parent
1e80f3db52
commit
75a36b5cd2
2 changed files with 31 additions and 4 deletions
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue