Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { StyleSheet, useUnistyles } from 'react-native-unistyles';
import { ToolViewProps } from './_all';
import { ToolSectionView } from '../ToolSectionView';
import { sessionAllow } from '@/sync/ops';
import { sync } from '@/sync/sync';
import { t } from '@/text';
import { Ionicons } from '@expo/vector-icons';

Expand Down Expand Up @@ -224,28 +223,24 @@ export const AskUserQuestionView = React.memo<ToolViewProps>(({ tool, sessionId
// captured the values above. TODO: Revisit this logic.
setIsSubmitted(true);

// Format answers as readable text
const responseLines: string[] = [];
const answers: Record<string, string> = {};
questions.forEach((q, qIndex) => {
const selected = selections.get(qIndex);
if (selected && selected.size > 0) {
const selectedLabels = Array.from(selected)
.map(optIndex => q.options[optIndex]?.label)
.filter(Boolean)
.join(', ');
responseLines.push(`${q.header}: ${selectedLabels}`);
answers[q.question] = selectedLabels;
}
});

const responseText = responseLines.join('\n');

try {
// 1. Approve the permission (like PermissionFooter.handleApprove does)
// AskUserQuestion expects answers to be returned as part of the tool input,
// not as a follow-up plain text message.
if (tool.permission?.id) {
await sessionAllow(sessionId, tool.permission.id);
await sessionAllow(sessionId, tool.permission.id, undefined, undefined, 'approved', { answers });
}
// 2. Send the answer as a message
await sync.sendMessage(sessionId, responseText);
} catch (error) {
console.error('Failed to submit answer:', error);
} finally {
Expand Down
9 changes: 5 additions & 4 deletions packages/happy-app/sources/sync/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface SessionPermissionRequest {
mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
allowTools?: string[];
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort';
updatedInput?: Record<string, unknown>;
}

// Mode change operation types
Expand Down Expand Up @@ -322,16 +323,16 @@ export async function sessionAbort(sessionId: string): Promise<void> {
/**
* Allow a permission request
*/
export async function sessionAllow(sessionId: string, id: string, mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan', allowedTools?: string[], decision?: 'approved' | 'approved_for_session'): Promise<void> {
const request: SessionPermissionRequest = { id, approved: true, mode, allowTools: allowedTools, decision };
export async function sessionAllow(sessionId: string, id: string, mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan', allowedTools?: string[], decision?: 'approved' | 'approved_for_session', updatedInput?: Record<string, unknown>): Promise<void> {
const request: SessionPermissionRequest = { id, approved: true, mode, allowTools: allowedTools, decision, updatedInput };
await apiSocket.sessionRPC(sessionId, 'permission', request);
}

/**
* Deny a permission request
*/
export async function sessionDeny(sessionId: string, id: string, mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan', allowedTools?: string[], decision?: 'denied' | 'abort'): Promise<void> {
const request: SessionPermissionRequest = { id, approved: false, mode, allowTools: allowedTools, decision };
export async function sessionDeny(sessionId: string, id: string, mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan', allowedTools?: string[], decision?: 'denied' | 'abort', updatedInput?: Record<string, unknown>): Promise<void> {
const request: SessionPermissionRequest = { id, approved: false, mode, allowTools: allowedTools, decision, updatedInput };
await apiSocket.sessionRPC(sessionId, 'permission', request);
}

Expand Down
7 changes: 6 additions & 1 deletion packages/happy-cli/src/claude/utils/permissionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface PermissionResponse {
reason?: string;
mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
allowTools?: string[];
updatedInput?: Record<string, unknown>;
receivedAt?: number;
}

Expand Down Expand Up @@ -102,8 +103,12 @@ export class PermissionHandler {
}
} else {
// Handle default case for all other tools
const originalInput = (pending.input as Record<string, unknown>) || {};
const updatedInput = response.updatedInput
? { ...originalInput, ...response.updatedInput }
: originalInput;
const result: PermissionResult = response.approved
? { behavior: 'allow', updatedInput: (pending.input as Record<string, unknown>) || {} }
? { behavior: 'allow', updatedInput }
: { behavior: 'deny', message: response.reason || `The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.` };

pending.resolve(result);
Expand Down