mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
fix: use queue for permission requests to prevent blocking
Changed pendingRequest from single value to pendingRequests queue. This prevents consecutive permission requests from overwriting each other, which caused the first request's Promise to never resolve. - permissionStore: pendingRequest → pendingRequests[] - Added addPendingRequest() and getCurrentRequest() - respondToRequest() now removes from queue head - Updated all consumers to use queue[0] Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ function AppContent(): React.JSX.Element {
|
||||
const setSidebarOpen = useUIStore((s) => s.setSidebarOpen)
|
||||
|
||||
// Permission state - get the session ID that has a pending permission request
|
||||
const pendingPermission = usePermissionStore((s) => s.pendingRequest)
|
||||
const pendingPermission = usePermissionStore((s) => s.pendingRequests[0] ?? null)
|
||||
const permissionPendingSessionId = pendingPermission?.multicaSessionId ?? null
|
||||
|
||||
// Modal actions
|
||||
|
||||
@@ -23,7 +23,7 @@ interface ChatViewProps {
|
||||
|
||||
export function ChatView({ updates, isProcessing, hasSession, isInitializing, currentSessionId, onSelectFolder }: ChatViewProps) {
|
||||
const bottomRef = useRef<HTMLDivElement>(null)
|
||||
const pendingPermission = usePermissionStore((s) => s.pendingRequest)
|
||||
const pendingPermission = usePermissionStore((s) => s.pendingRequests[0] ?? null)
|
||||
|
||||
// Only show permission request if it belongs to the current session
|
||||
const currentPermission = pendingPermission?.multicaSessionId === currentSessionId
|
||||
|
||||
@@ -12,12 +12,12 @@ import { StandardPermissionUI } from './StandardPermissionUI'
|
||||
import type { PermissionRequestItemProps, AskUserQuestionInput } from './types'
|
||||
|
||||
export function PermissionRequestItem({ request }: PermissionRequestItemProps) {
|
||||
const pendingRequest = usePermissionStore((s) => s.pendingRequest)
|
||||
const currentRequest = usePermissionStore((s) => s.pendingRequests[0] ?? null)
|
||||
const currentQuestionIndex = usePermissionStore((s) => s.currentQuestionIndex)
|
||||
const getRespondedRequest = usePermissionStore((s) => s.getRespondedRequest)
|
||||
|
||||
const { toolCall } = request
|
||||
const isPending = pendingRequest?.requestId === request.requestId
|
||||
const isPending = currentRequest?.requestId === request.requestId
|
||||
|
||||
// Get responded data for completed requests
|
||||
const respondedData = getRespondedRequest(request.requestId)
|
||||
|
||||
@@ -158,10 +158,10 @@ export function useApp(): AppState & AppActions {
|
||||
})
|
||||
|
||||
// Subscribe to permission requests
|
||||
const setPendingRequest = usePermissionStore.getState().setPendingRequest
|
||||
const addPendingRequest = usePermissionStore.getState().addPendingRequest
|
||||
const unsubPermission = window.electronAPI.onPermissionRequest((request) => {
|
||||
console.log('[useApp] Permission request received:', request)
|
||||
setPendingRequest(request)
|
||||
addPendingRequest(request)
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -44,8 +44,8 @@ export interface RespondedRequest {
|
||||
}
|
||||
|
||||
interface PermissionStore {
|
||||
// Current pending permission request
|
||||
pendingRequest: PermissionRequest | null
|
||||
// Pending permission requests queue (supports concurrent requests)
|
||||
pendingRequests: PermissionRequest[]
|
||||
|
||||
// Responded requests (requestId -> responded info)
|
||||
respondedRequests: Map<string, RespondedRequest>
|
||||
@@ -60,8 +60,11 @@ interface PermissionStore {
|
||||
// Collected answers for multi-question AskUserQuestion
|
||||
collectedAnswers: QuestionAnswer[]
|
||||
|
||||
// Set pending request
|
||||
setPendingRequest: (request: PermissionRequest | null) => void
|
||||
// Get current request (first in queue)
|
||||
getCurrentRequest: () => PermissionRequest | null
|
||||
|
||||
// Add pending request to queue
|
||||
addPendingRequest: (request: PermissionRequest) => void
|
||||
|
||||
// Respond to permission request (with optional data for AskUserQuestion)
|
||||
respondToRequest: (optionId: string, data?: PermissionResponseData) => void
|
||||
@@ -80,25 +83,36 @@ interface PermissionStore {
|
||||
}
|
||||
|
||||
export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
pendingRequest: null,
|
||||
pendingRequests: [],
|
||||
respondedRequests: new Map(),
|
||||
lastRespondedRequest: null,
|
||||
currentQuestionIndex: 0,
|
||||
collectedAnswers: [],
|
||||
|
||||
setPendingRequest: (request) => {
|
||||
// Clear last responded and reset multi-question state when a new request comes in
|
||||
set({
|
||||
pendingRequest: request,
|
||||
lastRespondedRequest: null,
|
||||
currentQuestionIndex: 0,
|
||||
collectedAnswers: [],
|
||||
getCurrentRequest: () => {
|
||||
return get().pendingRequests[0] || null
|
||||
},
|
||||
|
||||
addPendingRequest: (request) => {
|
||||
set((state) => {
|
||||
const isQueueEmpty = state.pendingRequests.length === 0
|
||||
return {
|
||||
pendingRequests: [...state.pendingRequests, request],
|
||||
// Only reset multi-question state when queue was empty
|
||||
...(isQueueEmpty ? {
|
||||
lastRespondedRequest: null,
|
||||
currentQuestionIndex: 0,
|
||||
collectedAnswers: [],
|
||||
} : {}),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
respondToRequest: (optionId, data) => {
|
||||
const { pendingRequest, respondedRequests } = get()
|
||||
if (!pendingRequest) {
|
||||
const { pendingRequests, respondedRequests } = get()
|
||||
const currentRequest = pendingRequests[0]
|
||||
|
||||
if (!currentRequest) {
|
||||
console.warn('[PermissionStore] No pending request to respond to')
|
||||
return
|
||||
}
|
||||
@@ -110,11 +124,12 @@ export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
selectedOptionsLength: data?.selectedOptions?.length,
|
||||
hasAnswers: !!data?.answers,
|
||||
answersLength: data?.answers?.length,
|
||||
queueLength: pendingRequests.length,
|
||||
})
|
||||
|
||||
// Create responded request object
|
||||
const respondedRequest: RespondedRequest = {
|
||||
request: pendingRequest,
|
||||
request: currentRequest,
|
||||
response: {
|
||||
optionId,
|
||||
selectedOption: data?.selectedOption,
|
||||
@@ -127,10 +142,10 @@ export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
|
||||
// Store the responded request for later reference
|
||||
const newResponded = new Map(respondedRequests)
|
||||
newResponded.set(pendingRequest.requestId, respondedRequest)
|
||||
newResponded.set(currentRequest.requestId, respondedRequest)
|
||||
|
||||
const response: PermissionResponse = {
|
||||
requestId: pendingRequest.requestId,
|
||||
requestId: currentRequest.requestId,
|
||||
optionId,
|
||||
data,
|
||||
}
|
||||
@@ -138,10 +153,10 @@ export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
// Send response to main process
|
||||
window.electronAPI.respondToPermission(response)
|
||||
|
||||
// Clear pending request, store responded, and set as last responded
|
||||
// Also reset multi-question state
|
||||
// Remove current request from queue (shift), store responded
|
||||
// Reset multi-question state for the next request
|
||||
set({
|
||||
pendingRequest: null,
|
||||
pendingRequests: pendingRequests.slice(1),
|
||||
respondedRequests: newResponded,
|
||||
lastRespondedRequest: respondedRequest,
|
||||
currentQuestionIndex: 0,
|
||||
@@ -150,14 +165,16 @@ export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
},
|
||||
|
||||
answerCurrentQuestion: (answer, isCustom = false) => {
|
||||
const { pendingRequest, currentQuestionIndex, collectedAnswers } = get()
|
||||
if (!pendingRequest) {
|
||||
const { pendingRequests, currentQuestionIndex, collectedAnswers } = get()
|
||||
const currentRequest = pendingRequests[0]
|
||||
|
||||
if (!currentRequest) {
|
||||
console.warn('[PermissionStore] No pending request for answerCurrentQuestion')
|
||||
return
|
||||
}
|
||||
|
||||
// Get questions from rawInput
|
||||
const rawInput = pendingRequest.toolCall.rawInput as AskUserQuestionInput | undefined
|
||||
const rawInput = currentRequest.toolCall.rawInput as AskUserQuestionInput | undefined
|
||||
const questions = rawInput?.questions || []
|
||||
const currentQuestion = questions[currentQuestionIndex]
|
||||
|
||||
@@ -190,9 +207,9 @@ export const usePermissionStore = create<PermissionStore>((set, get) => ({
|
||||
console.log(`[PermissionStore] All ${questions.length} questions answered, sending response`)
|
||||
|
||||
// Find allow option
|
||||
const allowOption = pendingRequest.options.find((o) => o.kind === 'allow_once') ||
|
||||
pendingRequest.options.find((o) => o.kind === 'allow') ||
|
||||
pendingRequest.options[0]
|
||||
const allowOption = currentRequest.options.find((o) => o.kind === 'allow_once') ||
|
||||
currentRequest.options.find((o) => o.kind === 'allow') ||
|
||||
currentRequest.options[0]
|
||||
|
||||
// Build response data with all answers
|
||||
const responseData: PermissionResponseData = {
|
||||
|
||||
Reference in New Issue
Block a user