π File detail
utils/queueProcessor.ts
π§© .tsπ 96 linesπΎ 3,174 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes processQueueIfReady and hasQueuedCommands β mainly functions, hooks, or classes. It composes internal code from types and messageQueueManager (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { QueuedCommand } from '../types/textInputTypes.js' import { dequeue, dequeueAllMatching, hasCommandsInQueue,
π€ Exports (heuristic)
processQueueIfReadyhasQueuedCommands
π₯οΈ Source preview
import type { QueuedCommand } from '../types/textInputTypes.js'
import {
dequeue,
dequeueAllMatching,
hasCommandsInQueue,
peek,
} from './messageQueueManager.js'
type ProcessQueueParams = {
executeInput: (commands: QueuedCommand[]) => Promise<void>
}
type ProcessQueueResult = {
processed: boolean
}
/**
* Check if a queued command is a slash command (value starts with '/').
*/
function isSlashCommand(cmd: QueuedCommand): boolean {
if (typeof cmd.value === 'string') {
return cmd.value.trim().startsWith('/')
}
// For ContentBlockParam[], check the first text block
for (const block of cmd.value) {
if (block.type === 'text') {
return block.text.trim().startsWith('/')
}
}
return false
}
/**
* Processes commands from the queue.
*
* Slash commands (starting with '/') and bash-mode commands are processed
* one at a time so each goes through the executeInput path individually.
* Bash commands need individual processing to preserve per-command error
* isolation, exit codes, and progress UI. Other non-slash commands are
* batched: all items **with the same mode** as the highest-priority item
* are drained at once and passed as a single array to executeInput β each
* becomes its own user message with its own UUID. Different modes
* (e.g. prompt vs task-notification) are never mixed because they are
* treated differently downstream.
*
* The caller is responsible for ensuring no query is currently running
* and for calling this function again after each command completes
* until the queue is empty.
*
* @returns result with processed status
*/
export function processQueueIfReady({
executeInput,
}: ProcessQueueParams): ProcessQueueResult {
// This processor runs on the REPL main thread between turns. Skip anything
// addressed to a subagent β an unfiltered peek() returning a subagent
// notification would set targetMode, dequeueAllMatching would find nothing
// matching that mode with agentId===undefined, and we'd return processed:
// false with the queue unchanged β the React effect never re-fires and any
// queued user prompt stalls permanently.
const isMainThread = (cmd: QueuedCommand) => cmd.agentId === undefined
const next = peek(isMainThread)
if (!next) {
return { processed: false }
}
// Slash commands and bash-mode commands are processed individually.
// Bash commands need per-command error isolation, exit codes, and progress UI.
if (isSlashCommand(next) || next.mode === 'bash') {
const cmd = dequeue(isMainThread)!
void executeInput([cmd])
return { processed: true }
}
// Drain all non-slash-command items with the same mode at once.
const targetMode = next.mode
const commands = dequeueAllMatching(
cmd => isMainThread(cmd) && !isSlashCommand(cmd) && cmd.mode === targetMode,
)
if (commands.length === 0) {
return { processed: false }
}
void executeInput(commands)
return { processed: true }
}
/**
* Checks if the queue has pending commands.
* Use this to determine if queue processing should be triggered.
*/
export function hasQueuedCommands(): boolean {
return hasCommandsInQueue()
}