* Core logic for executing user input without UI side effects. * * All commands arrive as `queuedCommands`. First command gets full treatment * (attachments, ideSelection, pastedContents with image resizing). Commands 2-N * get `skipAttachments` to avoid duplicating turn-level context.
(params: ExecuteUserInputParams)
| 406 | * get `skipAttachments` to avoid duplicating turn-level context. |
| 407 | */ |
| 408 | async function executeUserInput(params: ExecuteUserInputParams): Promise<void> { |
| 409 | const { |
| 410 | messages, |
| 411 | mainLoopModel, |
| 412 | ideSelection, |
| 413 | querySource, |
| 414 | queryGuard, |
| 415 | setToolJSX, |
| 416 | getToolUseContext, |
| 417 | setUserInputOnProcessing, |
| 418 | setAbortController, |
| 419 | onQuery, |
| 420 | setAppState, |
| 421 | onBeforeQuery, |
| 422 | resetHistory, |
| 423 | canUseTool, |
| 424 | queuedCommands, |
| 425 | } = params |
| 426 | |
| 427 | // Note: paste references are already processed before calling this function |
| 428 | // (either in handlePromptSubmit before queuing, or before initial execution). |
| 429 | // Always create a fresh abort controller — queryGuard guarantees no concurrent |
| 430 | // executeUserInput call, so there's no prior controller to inherit. |
| 431 | const abortController = createAbortController() |
| 432 | setAbortController(abortController) |
| 433 | |
| 434 | function makeContext(): ProcessUserInputContext { |
| 435 | return getToolUseContext(messages, [], abortController, mainLoopModel) |
| 436 | } |
| 437 | |
| 438 | // Wrap in try-finally so the guard is released even if processUserInput |
| 439 | // throws or onQuery is skipped. onQuery's finally calls queryGuard.end(), |
| 440 | // which transitions running→idle; cancelReservation() below is a no-op in |
| 441 | // that case (only acts on dispatching state). |
| 442 | try { |
| 443 | // Reserve the guard BEFORE processUserInput — processBashCommand awaits |
| 444 | // BashTool.call() and processSlashCommand awaits getMessagesForSlashCommand, |
| 445 | // so the guard must be active during those awaits to ensure concurrent |
| 446 | // handlePromptSubmit calls queue (via the isActive check above) instead |
| 447 | // of starting a second executeUserInput. This call is a no-op if the |
| 448 | // guard is already in dispatching (legacy queue-processor path). |
| 449 | queryGuard.reserve() |
| 450 | queryCheckpoint('query_process_user_input_start') |
| 451 | |
| 452 | const newMessages: Message[] = [] |
| 453 | let shouldQuery = false |
| 454 | let allowedTools: string[] | undefined |
| 455 | let model: string | undefined |
| 456 | let effort: EffortValue | undefined |
| 457 | let nextInput: string | undefined |
| 458 | let submitNextInput: boolean | undefined |
| 459 | |
| 460 | // Iterate all commands uniformly. First command gets attachments + |
| 461 | // ideSelection + pastedContents, rest skip attachments to avoid |
| 462 | // duplicating turn-level context (IDE selection, todos, diffs). |
| 463 | let commands = queuedCommands ?? [] |
| 464 | const queuedAutonomyClaim = |
| 465 | await claimConsumableQueuedAutonomyCommands(commands) |
no test coverage detected