mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(chat): suppress 403 console spam from polling unpersisted conversations (ISSUE #408)
This commit is contained in:
parent
2f12c269f4
commit
e4e7b4c377
@ -82,6 +82,16 @@ export const useGoalStore = defineStore('goal', () => {
|
|||||||
activeGoalByConv.value[conversationId] = goal
|
activeGoalByConv.value[conversationId] = goal
|
||||||
return goal
|
return goal
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// A conversation that isn't persisted/owned yet — a brand-new empty chat
|
||||||
|
// before the first message lands, or a thread owned by another user —
|
||||||
|
// answers 403 (not the owner) / 404 (not found). That just means "no
|
||||||
|
// active goal yet": record the no-goal state silently. Only genuinely
|
||||||
|
// unexpected failures (network / 5xx) are worth a console error.
|
||||||
|
const status = (e as any)?.response?.status ?? (e as any)?.response?.data?.code
|
||||||
|
if (status === 403 || status === 404) {
|
||||||
|
activeGoalByConv.value[conversationId] = null
|
||||||
|
return null
|
||||||
|
}
|
||||||
console.error('[goal] loadActiveForConversation failed', e)
|
console.error('[goal] loadActiveForConversation failed', e)
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1001,6 +1001,25 @@ const activeCronRuns = ref<ActiveCronRun[]>([])
|
|||||||
function isCronConversation(cid: string | null | undefined): boolean {
|
function isCronConversation(cid: string | null | undefined): boolean {
|
||||||
return !!cid && (cid.startsWith('tasks_') || cid.startsWith('cron_'))
|
return !!cid && (cid.startsWith('tasks_') || cid.startsWith('cron_'))
|
||||||
}
|
}
|
||||||
|
// True when a conversation id was minted client-side for a brand-new chat
|
||||||
|
// the user hasn't sent anything in yet, so the backend has never persisted
|
||||||
|
// (and therefore can't "own") it. Every per-conversation endpoint called with
|
||||||
|
// such an id is rejected with 403 "无权访问该会话" — the status/messages poller
|
||||||
|
// and the goal loader both skip the request entirely for these ids instead of
|
||||||
|
// spamming the console with ownership errors.
|
||||||
|
//
|
||||||
|
// Conservative by construction: an id counts as ephemeral only when it is
|
||||||
|
// absent from BOTH the loaded conversation list AND the local message buffer,
|
||||||
|
// so a freshly-persisted conversation is never mistaken for one. At worst a
|
||||||
|
// single stray 403 slips through during the persist race, which the leaf
|
||||||
|
// callers swallow.
|
||||||
|
function isEphemeralConversation(cid: string | null | undefined): boolean {
|
||||||
|
if (!cid) return true
|
||||||
|
// Server-managed virtual conversations (cron / scheduled tasks) always exist.
|
||||||
|
if (cid.startsWith('tasks_') || cid.startsWith('cron_')) return false
|
||||||
|
if (conversations.value.some((c) => c.conversationId === cid)) return false
|
||||||
|
return !messages.value.some((m: any) => m.conversationId === cid)
|
||||||
|
}
|
||||||
async function refreshActiveCronRuns(cid: string) {
|
async function refreshActiveCronRuns(cid: string) {
|
||||||
if (!isCronConversation(cid)) {
|
if (!isCronConversation(cid)) {
|
||||||
activeCronRuns.value = []
|
activeCronRuns.value = []
|
||||||
@ -1065,8 +1084,13 @@ async function pollActivity() {
|
|||||||
} catch {
|
} catch {
|
||||||
// 静默失败,下一轮再试
|
// 静默失败,下一轮再试
|
||||||
}
|
}
|
||||||
// 自己没在生成时才刷新当前选中会话的消息 + 探测是否该接入流
|
// 自己没在生成时才刷新当前选中会话的消息 + 探测是否该接入流。
|
||||||
if (currentConversationId.value && !isGenerating.value && streamPhase.value !== 'awaiting_approval') {
|
// 跳过尚未落库的临时会话(新建空会话、还没发消息):后端查不到会话行,
|
||||||
|
// getStatus / listMessages 一律 403,4s 轮询会把 console 刷爆(见 issue #408)。
|
||||||
|
if (currentConversationId.value
|
||||||
|
&& !isGenerating.value
|
||||||
|
&& streamPhase.value !== 'awaiting_approval'
|
||||||
|
&& !isEphemeralConversation(currentConversationId.value)) {
|
||||||
const cid = currentConversationId.value
|
const cid = currentConversationId.value
|
||||||
try {
|
try {
|
||||||
const statusRes: any = await conversationApi.getStatus(cid)
|
const statusRes: any = await conversationApi.getStatus(cid)
|
||||||
@ -1157,7 +1181,10 @@ const goalStore = useGoalStore()
|
|||||||
const workspaceStoreForGoal = useWorkspaceStore()
|
const workspaceStoreForGoal = useWorkspaceStore()
|
||||||
const currentWorkspaceId = computed(() => workspaceStoreForGoal.currentWorkspaceId ?? '1')
|
const currentWorkspaceId = computed(() => workspaceStoreForGoal.currentWorkspaceId ?? '1')
|
||||||
watch(currentConversationId, async (cid) => {
|
watch(currentConversationId, async (cid) => {
|
||||||
if (cid) {
|
// Skip un-persisted conversations: a brand-new empty chat has no goal yet
|
||||||
|
// and the lookup would only 403 (Not the owner). The ring is hydrated by the
|
||||||
|
// goal_created SSE event once the first turn lands.
|
||||||
|
if (cid && !isEphemeralConversation(cid)) {
|
||||||
await goalStore.loadActiveForConversation(cid)
|
await goalStore.loadActiveForConversation(cid)
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
@ -1170,7 +1197,9 @@ watch(currentConversationId, async (cid) => {
|
|||||||
// this transition-to-idle refresh is what keeps the goal ring honest after
|
// this transition-to-idle refresh is what keeps the goal ring honest after
|
||||||
// every turn against the persisted truth.
|
// every turn against the persisted truth.
|
||||||
watch(isGenerating, async (generating, wasGenerating) => {
|
watch(isGenerating, async (generating, wasGenerating) => {
|
||||||
if (wasGenerating && !generating && currentConversationId.value) {
|
if (wasGenerating && !generating
|
||||||
|
&& currentConversationId.value
|
||||||
|
&& !isEphemeralConversation(currentConversationId.value)) {
|
||||||
await goalStore.loadActiveForConversation(currentConversationId.value)
|
await goalStore.loadActiveForConversation(currentConversationId.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user