source dump of claude code
at main 95 lines 3.2 kB view raw
1import type { QueuedCommand } from '../types/textInputTypes.js' 2import { 3 dequeue, 4 dequeueAllMatching, 5 hasCommandsInQueue, 6 peek, 7} from './messageQueueManager.js' 8 9type ProcessQueueParams = { 10 executeInput: (commands: QueuedCommand[]) => Promise<void> 11} 12 13type ProcessQueueResult = { 14 processed: boolean 15} 16 17/** 18 * Check if a queued command is a slash command (value starts with '/'). 19 */ 20function isSlashCommand(cmd: QueuedCommand): boolean { 21 if (typeof cmd.value === 'string') { 22 return cmd.value.trim().startsWith('/') 23 } 24 // For ContentBlockParam[], check the first text block 25 for (const block of cmd.value) { 26 if (block.type === 'text') { 27 return block.text.trim().startsWith('/') 28 } 29 } 30 return false 31} 32 33/** 34 * Processes commands from the queue. 35 * 36 * Slash commands (starting with '/') and bash-mode commands are processed 37 * one at a time so each goes through the executeInput path individually. 38 * Bash commands need individual processing to preserve per-command error 39 * isolation, exit codes, and progress UI. Other non-slash commands are 40 * batched: all items **with the same mode** as the highest-priority item 41 * are drained at once and passed as a single array to executeInput — each 42 * becomes its own user message with its own UUID. Different modes 43 * (e.g. prompt vs task-notification) are never mixed because they are 44 * treated differently downstream. 45 * 46 * The caller is responsible for ensuring no query is currently running 47 * and for calling this function again after each command completes 48 * until the queue is empty. 49 * 50 * @returns result with processed status 51 */ 52export function processQueueIfReady({ 53 executeInput, 54}: ProcessQueueParams): ProcessQueueResult { 55 // This processor runs on the REPL main thread between turns. Skip anything 56 // addressed to a subagent — an unfiltered peek() returning a subagent 57 // notification would set targetMode, dequeueAllMatching would find nothing 58 // matching that mode with agentId===undefined, and we'd return processed: 59 // false with the queue unchanged → the React effect never re-fires and any 60 // queued user prompt stalls permanently. 61 const isMainThread = (cmd: QueuedCommand) => cmd.agentId === undefined 62 63 const next = peek(isMainThread) 64 if (!next) { 65 return { processed: false } 66 } 67 68 // Slash commands and bash-mode commands are processed individually. 69 // Bash commands need per-command error isolation, exit codes, and progress UI. 70 if (isSlashCommand(next) || next.mode === 'bash') { 71 const cmd = dequeue(isMainThread)! 72 void executeInput([cmd]) 73 return { processed: true } 74 } 75 76 // Drain all non-slash-command items with the same mode at once. 77 const targetMode = next.mode 78 const commands = dequeueAllMatching( 79 cmd => isMainThread(cmd) && !isSlashCommand(cmd) && cmd.mode === targetMode, 80 ) 81 if (commands.length === 0) { 82 return { processed: false } 83 } 84 85 void executeInput(commands) 86 return { processed: true } 87} 88 89/** 90 * Checks if the queue has pending commands. 91 * Use this to determine if queue processing should be triggered. 92 */ 93export function hasQueuedCommands(): boolean { 94 return hasCommandsInQueue() 95}