mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 12:27:53 +08:00
Keep rejected managed approval pending in UI
This commit is contained in:
parent
90c9256dc6
commit
3e9377458d
@ -824,13 +824,18 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
|||||||
currentAssistantId.value = null
|
currentAssistantId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
streamPhase.value = data.status === 'awaiting_approval' ? 'awaiting_approval'
|
streamPhase.value = errorFired ? 'idle'
|
||||||
|
: data.status === 'awaiting_approval' ? 'awaiting_approval'
|
||||||
: data.status === 'stopped' ? 'stopped' : 'completed'
|
: data.status === 'stopped' ? 'stopped' : 'completed'
|
||||||
if (data.status !== 'awaiting_approval') {
|
if (data.status !== 'awaiting_approval') {
|
||||||
phaseInfo.value = null
|
phaseInfo.value = null
|
||||||
compactStatus.value = null
|
compactStatus.value = null
|
||||||
lifecycleStage.value = null
|
lifecycleStage.value = null
|
||||||
expirePendingApprovals(data.status === 'stopped' ? 'stopped' : 'completed')
|
// An error may be followed by a protocol-level done event. Its status
|
||||||
|
// does not resolve an approval or turn the failed request into success.
|
||||||
|
if (!errorFired) {
|
||||||
|
expirePendingApprovals(data.status === 'stopped' ? 'stopped' : 'completed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety cleanup for queue state (no-op if queued_input_started already handled it)
|
// Safety cleanup for queue state (no-op if queued_input_started already handled it)
|
||||||
@ -846,13 +851,15 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
|||||||
: data.status === 'interrupted' ? 'interrupted'
|
: data.status === 'interrupted' ? 'interrupted'
|
||||||
: data.status === 'awaiting_approval' ? 'awaiting_approval'
|
: data.status === 'awaiting_approval' ? 'awaiting_approval'
|
||||||
: 'completed'
|
: 'completed'
|
||||||
onStreamEnd?.({
|
if (!errorFired) {
|
||||||
conversationId: data.conversationId || streamConversationId,
|
onStreamEnd?.({
|
||||||
reason,
|
conversationId: data.conversationId || streamConversationId,
|
||||||
assistantMessageId: data.assistantMessageId,
|
reason,
|
||||||
persisted: data.persisted,
|
assistantMessageId: data.assistantMessageId,
|
||||||
messageCount: data.messageCount,
|
persisted: data.persisted,
|
||||||
})
|
messageCount: data.messageCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Re-attach SSE if any generative task is still in flight, so the eventual
|
// Re-attach SSE if any generative task is still in flight, so the eventual
|
||||||
// async_task_completed event reaches us live (otherwise the user has to
|
// async_task_completed event reaches us live (otherwise the user has to
|
||||||
@ -861,7 +868,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
|||||||
const reconnectableStatus = !data.status
|
const reconnectableStatus = !data.status
|
||||||
|| data.status === 'completed'
|
|| data.status === 'completed'
|
||||||
|| data.status === 'idle'
|
|| data.status === 'idle'
|
||||||
if (reconnectableStatus
|
if (!errorFired && reconnectableStatus
|
||||||
&& !reconnectingForAsyncTasks
|
&& !reconnectingForAsyncTasks
|
||||||
&& pendingAsyncTaskIds.size > 0
|
&& pendingAsyncTaskIds.size > 0
|
||||||
&& streamConversationId) {
|
&& streamConversationId) {
|
||||||
@ -918,7 +925,8 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
|||||||
lifecycleStage.value = null
|
lifecycleStage.value = null
|
||||||
// Clear queue on error to avoid stale state
|
// Clear queue on error to avoid stale state
|
||||||
messageQueue.clear()
|
messageQueue.clear()
|
||||||
expirePendingApprovals('failed')
|
// The approval may still be pending after a rejected request. The view
|
||||||
|
// reconciles it against the server's pending list in onStreamEnd.
|
||||||
|
|
||||||
if (errorFired) return
|
if (errorFired) return
|
||||||
errorFired = true
|
errorFired = true
|
||||||
|
|||||||
@ -808,6 +808,32 @@ const {
|
|||||||
// 流结束后刷新会话列表(更新 lastActiveTime / 标题等)
|
// 流结束后刷新会话列表(更新 lastActiveTime / 标题等)
|
||||||
await loadConversations()
|
await loadConversations()
|
||||||
if (meta.conversationId && meta.conversationId === currentConversationId.value) {
|
if (meta.conversationId && meta.conversationId === currentConversationId.value) {
|
||||||
|
if (meta.reason === 'error') {
|
||||||
|
// Keep the failed turn visible, but take approval state from the
|
||||||
|
// server. A rejected approval SSE error leaves its pending row open.
|
||||||
|
try {
|
||||||
|
const approvalRes: any = await chatApi.getPendingApprovals(meta.conversationId)
|
||||||
|
if (meta.conversationId !== currentConversationId.value) return
|
||||||
|
const serverIds = new Set<string>((approvalRes.data || []).map((p: any) => p.pendingId))
|
||||||
|
messages.value = messages.value.map((m) => {
|
||||||
|
const pending = (m as any).metadata?.pendingApproval
|
||||||
|
if (!pending?.pendingId || pending.status !== 'pending_approval') return m
|
||||||
|
const active = serverIds.has(pending.pendingId)
|
||||||
|
return {
|
||||||
|
...m,
|
||||||
|
status: active ? 'awaiting_approval'
|
||||||
|
: m.status === 'awaiting_approval' ? 'failed' : m.status,
|
||||||
|
metadata: {
|
||||||
|
...(m as any).metadata,
|
||||||
|
currentPhase: active ? 'awaiting_approval' : undefined,
|
||||||
|
pendingApproval: { ...pending, status: active ? 'pending_approval' : 'expired' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// Keep the local pending card if the authoritative read is unavailable.
|
||||||
|
}
|
||||||
|
}
|
||||||
// Skip DB refresh for awaiting_approval / interrupted / error:
|
// Skip DB refresh for awaiting_approval / interrupted / error:
|
||||||
// - awaiting_approval / interrupted: avoids overwriting local-only state
|
// - awaiting_approval / interrupted: avoids overwriting local-only state
|
||||||
// or breaking message ordering.
|
// or breaking message ordering.
|
||||||
@ -2065,10 +2091,6 @@ async function handleSendMessage(content: string, pendingApprovalId?: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 乐观更新审批状态
|
|
||||||
const decision = trimmed === '/approve' ? 'approved' : 'denied'
|
|
||||||
;(pendingMsg as any).metadata.pendingApproval.status = decision
|
|
||||||
|
|
||||||
inputText.value = ''
|
inputText.value = ''
|
||||||
chatInputRef.value?.clear?.()
|
chatInputRef.value?.clear?.()
|
||||||
|
|
||||||
@ -2082,8 +2104,6 @@ async function handleSendMessage(content: string, pendingApprovalId?: string) {
|
|||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('Approval stream failed:', e)
|
console.error('Approval stream failed:', e)
|
||||||
// 回滚乐观更新
|
|
||||||
;(pendingMsg as any).metadata.pendingApproval.status = 'pending_approval'
|
|
||||||
mcToast.error(e?.message || 'Approval failed')
|
mcToast.error(e?.message || 'Approval failed')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user