diff --git a/core/http/react-ui/src/pages/AgentChat.jsx b/core/http/react-ui/src/pages/AgentChat.jsx index ae4f5c83b..13b83f2b0 100644 --- a/core/http/react-ui/src/pages/AgentChat.jsx +++ b/core/http/react-ui/src/pages/AgentChat.jsx @@ -8,7 +8,7 @@ import CanvasPanel from '../components/CanvasPanel' import ResourceCards from '../components/ResourceCards' import ConfirmDialog from '../components/ConfirmDialog' import { useAgentChat } from '../hooks/useAgentChat' -import { relativeTime } from '../utils/format' +import { relativeTime, normalizeTimestampMs } from '../utils/format' import { copyToClipboard } from '../utils/clipboard' function getLastMessagePreview(conv) { @@ -139,8 +139,9 @@ export default function AgentChat() { id: nextId(), sender, content: data.content || data.message || '', - // Backend sends Unix milliseconds (see core/services/agents events). - timestamp: data.timestamp || Date.now(), + // Backend timestamp encoding varies by deploy mode (RFC3339 string, + // Unix ms, or Unix ns); normalize to JS milliseconds. + timestamp: normalizeTimestampMs(data.timestamp), } if (data.metadata && Object.keys(data.metadata).length > 0) { msg.metadata = data.metadata diff --git a/core/http/react-ui/src/utils/format.js b/core/http/react-ui/src/utils/format.js index b17006e08..1cf162028 100644 --- a/core/http/react-ui/src/utils/format.js +++ b/core/http/react-ui/src/utils/format.js @@ -12,6 +12,26 @@ export function percentColor(pct) { return 'var(--color-success)' } +// normalizeTimestampMs converts a timestamp emitted by the backend into JS +// milliseconds, regardless of its encoding. The agent SSE bridge emits the +// json_message timestamp in three different shapes depending on deploy mode: +// an RFC3339 string (standalone agent pool), Unix milliseconds (local +// dispatcher), or Unix nanoseconds (older NATS path). A numeric value is +// classified by magnitude (s / ms / us / ns) so any of them yields a sane +// epoch. Falls back to Date.now() for null/empty/unparseable input. +export function normalizeTimestampMs(ts) { + if (ts === null || ts === undefined || ts === '') return Date.now() + if (typeof ts === 'string') { + const parsed = Date.parse(ts) + return Number.isNaN(parsed) ? Date.now() : parsed + } + if (typeof ts !== 'number' || !Number.isFinite(ts)) return Date.now() + if (ts > 1e17) return Math.floor(ts / 1e6) // nanoseconds + if (ts > 1e14) return Math.floor(ts / 1e3) // microseconds + if (ts > 1e11) return ts // milliseconds + return ts * 1000 // seconds +} + export function formatTimestamp(ts) { if (!ts) return '-' const d = new Date(ts)