feat: add ask_user interactive user input to frontend

Wire the sidecar user-input-requested protocol event through the main
process, IPC layer, and renderer UI so agents can ask the user
interactive questions with choices and freeform input.

- Add UserInputRequestedEvent and ResolveUserInputCommand to sidecar
  protocol types
- Add resolveUserInput method to SidecarClient and onUserInput callback
  to runTurn
- Create PendingUserInputRecord domain type and add pendingUserInput to
  SessionRecord
- Add handleUserInputRequested and resolveSessionUserInput to
  AryxAppService with handle management
- Register sessions:resolve-user-input IPC channel with preload bridge
- Create UserInputBanner component with choice buttons and freeform
  input
- Integrate UserInputBanner into ChatPane with header indicator

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David KayaandCopilot committed 2026-03-26 17:45:27 +01:00
1 parent 2ae4bd01b4
commit f15b1aedb1
14 files changed
+356 -12

No files matched your search

+1
View File
@@ -26,6 +26,7 @@ export const ipcChannels = {
sendSessionMessage: 'sessions:send-message',
cancelSessionTurn: 'sessions:cancel-turn',
resolveSessionApproval: 'sessions:resolve-approval',
resolveSessionUserInput: 'sessions:resolve-user-input',
querySessions: 'sessions:query',
updateSessionModelConfig: 'sessions:update-model-config',
selectProject: 'selection:project',
+8
View File
@@ -36,6 +36,13 @@ export interface ResolveSessionApprovalInput {
decision: ApprovalDecision;
}
export interface ResolveSessionUserInputInput {
sessionId: string;
userInputId: string;
answer: string;
wasFreeform: boolean;
}
export interface UpdateSessionModelConfigInput {
sessionId: string;
model: string;
@@ -126,6 +133,7 @@ export interface ElectronApi {
sendSessionMessage(input: SendSessionMessageInput): Promise<void>;
cancelSessionTurn(input: CancelSessionTurnInput): Promise<void>;
resolveSessionApproval(input: ResolveSessionApprovalInput): Promise<WorkspaceState>;
resolveSessionUserInput(input: ResolveSessionUserInputInput): Promise<WorkspaceState>;
updateSessionModelConfig(input: UpdateSessionModelConfigInput): Promise<WorkspaceState>;
querySessions(input: QuerySessionsInput): Promise<SessionQueryResult[]>;
selectProject(projectId?: string): Promise<WorkspaceState>;
+23 -1
View File
@@ -92,12 +92,21 @@ export interface ResolveApprovalCommand {
decision: ApprovalDecision;
}
export interface ResolveUserInputCommand {
type: 'resolve-user-input';
requestId: string;
userInputId: string;
answer: string;
wasFreeform: boolean;
}
export type SidecarCommand =
| DescribeCapabilitiesCommand
| ValidatePatternCommand
| RunTurnCommand
| CancelTurnCommand
| ResolveApprovalCommand;
| ResolveApprovalCommand
| ResolveUserInputCommand;
export interface RunTurnLocalMcpServerConfig {
id: string;
@@ -220,6 +229,18 @@ export interface ApprovalRequestedEvent {
permissionDetail?: PermissionDetail;
}
export interface UserInputRequestedEvent {
type: 'user-input-requested';
requestId: string;
sessionId: string;
userInputId: string;
agentId?: string;
agentName?: string;
question: string;
choices?: string[];
allowFreeform?: boolean;
}
export interface CommandErrorEvent {
type: 'command-error';
requestId: string;
@@ -238,5 +259,6 @@ export type SidecarEvent =
| TurnCompleteEvent
| AgentActivityEvent
| ApprovalRequestedEvent
| UserInputRequestedEvent
| CommandErrorEvent
| CommandCompleteEvent;
+2
View File
@@ -11,6 +11,7 @@ import {
type SessionApprovalSettings,
} from '@shared/domain/approval';
import type { SessionRunRecord } from '@shared/domain/runTimeline';
import type { PendingUserInputRecord } from '@shared/domain/userInput';
export type ChatRole = 'system' | 'user' | 'assistant';
export type SessionStatus = 'idle' | 'running' | 'error';
@@ -48,6 +49,7 @@ export interface SessionRecord {
approvalSettings?: SessionApprovalSettings;
pendingApproval?: PendingApprovalRecord;
pendingApprovalQueue?: PendingApprovalRecord[];
pendingUserInput?: PendingUserInputRecord;
runs: SessionRunRecord[];
}
+14
View File
@@ -0,0 +1,14 @@
export type UserInputStatus = 'pending' | 'answered';
export interface PendingUserInputRecord {
id: string;
status: UserInputStatus;
agentId?: string;
agentName?: string;
question: string;
choices?: string[];
allowFreeform: boolean;
requestedAt: string;
answer?: string;
answeredAt?: string;
}