feat: harden support, localize knowledge and add operator availability [skip ci]

Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -1,8 +1,12 @@
// Modified in the independent hardening fork; see docs/hardening/README.md.
// Modified for AgentDeskFixes: bind realtime work to the current session and sanitize diagnostics.
"use client"
import { useEffect, useRef } from "react"
import { toast } from "sonner"
import { useSession } from "@/components/session-provider"
import { realtimeLogContext } from "@/lib/realtime-log-context"
import { createAdminWebSocketUrl } from "@/lib/api/admin"
import { type AgentMessage } from "@/lib/api/agent"
import { shouldReloadConversationListForRealtimePatch } from "@/lib/agent-conversation-realtime"
@@ -50,6 +54,8 @@ export function sendOperatorPresence(
export function useAgentConversationRealtime() {
const t = useI18n()
const { session } = useSession()
const accessToken = session?.accessToken
const selectedConversationId = useAgentConversationsStore(
(state) => state.selectedConversationId
)
@@ -59,19 +65,22 @@ export function useAgentConversationRealtime() {
const realtimeRef = useRef<AgentRealtimeConnection | null>(null)
const subscribedConversationIdRef = useRef<number | null>(null)
const selectedConversationIdRef = useRef<number | null>(selectedConversationId)
const currentUserIdRef = useRef<number>(readSession()?.user.id ?? 0)
const currentUserId = session?.user.id ?? 0
useEffect(() => {
selectedConversationIdRef.current = selectedConversationId
}, [selectedConversationId])
useEffect(() => {
if (!accessToken) return
const isCurrentSession = () => readSession()?.accessToken === accessToken
const realtime = createRealtimeConnectionManager({
createSocket: () => new WebSocket(createAdminWebSocketUrl()),
onStatusChange: setRealtimeStatus,
onStatusChange: (status) => { if (isCurrentSession()) setRealtimeStatus(status) },
onOpen: (socket) => {
if (!isCurrentSession()) return
console.info("[agent-realtime] websocket connected", {
url: socket.url,
...realtimeLogContext(socket.url),
})
const conversationId = selectedConversationIdRef.current
@@ -95,6 +104,7 @@ export function useAgentConversationRealtime() {
}
},
onMessage: (event, socket) => {
if (!isCurrentSession()) return
try {
const envelope = JSON.parse(event.data) as AgentRealtimeEnvelope
const eventType = envelope.type ?? ""
@@ -130,7 +140,7 @@ export function useAgentConversationRealtime() {
}
if (eventType === "operator.presence" && payload) {
store.applyRealtimeOperatorPresence(payload, currentUserIdRef.current)
store.applyRealtimeOperatorPresence(payload, currentUserId)
return
}
@@ -145,7 +155,7 @@ export function useAgentConversationRealtime() {
store.applyRealtimeMessageCreated(message)
const isCustomerMsg = message.senderType === "customer"
const isAssignedToMe = (payload?.currentAssigneeId ?? 0) === currentUserIdRef.current
const isAssignedToMe = (payload?.currentAssigneeId ?? 0) === currentUserId
const isUnassigned = (payload?.currentAssigneeId ?? 0) === 0
const isCurrentActive = message.conversationId === selectedConversationIdRef.current
@@ -182,17 +192,16 @@ export function useAgentConversationRealtime() {
},
onClose: (event, socket) => {
console.log("[agent-realtime] websocket closed", {
url: socket.url,
...realtimeLogContext(socket.url),
readyState: socket.readyState,
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
})
subscribedConversationIdRef.current = null
},
onError: (_event, socket) => {
console.log("[agent-realtime] websocket error", {
url: socket.url,
...realtimeLogContext(socket.url),
readyState: socket.readyState,
})
},
@@ -225,7 +234,7 @@ export function useAgentConversationRealtime() {
realtime.disconnect()
subscribedConversationIdRef.current = null
}
}, [setRealtimeStatus, t])
}, [accessToken, currentUserId, setRealtimeStatus, t])
useEffect(() => {
const socket = realtimeRef.current?.getSocket()