diff --git a/desktop/src/pages/ActiveSession.test.tsx b/desktop/src/pages/ActiveSession.test.tsx
index a62803a1..446225e4 100644
--- a/desktop/src/pages/ActiveSession.test.tsx
+++ b/desktop/src/pages/ActiveSession.test.tsx
@@ -135,7 +135,7 @@ describe('ActiveSession task polling', () => {
expect(screen.getByTestId('chat-input')).toHaveAttribute('data-variant', 'default')
})
- it('shows the current goal as a persistent session status panel', () => {
+ it('does not duplicate the current goal as a page-level status panel', () => {
const sessionId = 'goal-visible-session'
useSessionStore.setState({
@@ -199,15 +199,11 @@ describe('ActiveSession task polling', () => {
render(
)
- const panel = screen.getByTestId('active-goal-panel')
- expect(panel).toHaveTextContent('当前目标')
- expect(panel).toHaveTextContent('自循环运行中')
- expect(panel).toHaveTextContent('ship the smoke test')
- expect(panel).toHaveTextContent('预算 0 / 2,000 tokens')
- expect(panel).toHaveTextContent('续作次数 0')
+ expect(screen.queryByTestId('active-goal-panel')).not.toBeInTheDocument()
+ expect(screen.getByTestId('message-list')).toBeInTheDocument()
})
- it('shows background agent progress below the goal panel', () => {
+ it('does not render background agent progress as a page-level panel', () => {
const sessionId = 'background-agent-visible-session'
useSessionStore.setState({
@@ -277,11 +273,8 @@ describe('ActiveSession task polling', () => {
render(
)
- const panel = screen.getByTestId('background-agent-panel')
- expect(panel).toHaveTextContent('后台 Agent')
- expect(panel).toHaveTextContent('运行中')
- expect(panel).toHaveTextContent('local_agent')
- expect(panel).toHaveTextContent('Running Playwright checks')
+ expect(screen.queryByTestId('background-agent-panel')).not.toBeInTheDocument()
+ expect(screen.getByTestId('message-list')).toBeInTheDocument()
})
it('refreshes CLI tasks repeatedly while a turn is active', async () => {
diff --git a/desktop/src/pages/ActiveSession.tsx b/desktop/src/pages/ActiveSession.tsx
index eac16e60..f323533c 100644
--- a/desktop/src/pages/ActiveSession.tsx
+++ b/desktop/src/pages/ActiveSession.tsx
@@ -28,8 +28,6 @@ import { TerminalSettings } from './TerminalSettings'
import type { SessionListItem } from '../types/session'
import { useMobileViewport } from '../hooks/useMobileViewport'
import { isTauriRuntime } from '../lib/desktopRuntime'
-import type { ActiveGoalState, BackgroundAgentTask } from '../types/chat'
-import { Bot, CheckCircle2, LoaderCircle, Target, XCircle } from 'lucide-react'
const TASK_POLL_INTERVAL_MS = 1000
const WORKSPACE_RESIZE_STEP = 32
@@ -201,165 +199,6 @@ function TerminalResizeHandle() {
)
}
-function GoalStatusPanel({
- goal,
- isRunning,
- compact,
-}: {
- goal: ActiveGoalState
- isRunning: boolean
- compact: boolean
-}) {
- const t = useTranslation()
- const stateLabel = goal.action === 'completed'
- ? t('chat.activeGoal.completed')
- : goal.action === 'paused' || goal.status === 'paused'
- ? t('chat.activeGoal.paused')
- : isRunning && goal.status !== 'complete'
- ? t('chat.activeGoal.running')
- : t('chat.activeGoal.active')
- const hasMeta = Boolean(goal.budget || goal.continuations || goal.elapsed)
-
- return (
-
- )
-}
-
-function formatBackgroundTaskDuration(durationMs?: number) {
- if (typeof durationMs !== 'number' || durationMs < 0) return null
- const seconds = Math.round(durationMs / 1000)
- if (seconds < 60) return `${seconds}s`
- const minutes = Math.floor(seconds / 60)
- return `${minutes}m ${seconds % 60}s`
-}
-
-function BackgroundAgentTasksPanel({
- tasks,
- compact,
-}: {
- tasks: BackgroundAgentTask[]
- compact: boolean
-}) {
- const t = useTranslation()
- if (tasks.length === 0) return null
-
- return (
-
- )
-}
-
export function ActiveSession() {
const isMobileLayout = useMobileViewport() && !isTauriRuntime()
const activeTabId = useTabStore((s) => s.activeTabId)
@@ -372,16 +211,6 @@ export function ActiveSession() {
const trackedTaskSessionId = useCLITaskStore((s) => s.sessionId)
const hasIncompleteTasks = useCLITaskStore((s) => s.tasks.some((task) => task.status !== 'completed'))
const chatState = sessionState?.chatState ?? 'idle'
- const activeGoal = sessionState?.activeGoal ?? null
- const backgroundAgentTasks = useMemo(
- () => Object.values(sessionState?.backgroundAgentTasks ?? {})
- .sort((a, b) => {
- if (a.status === 'running' && b.status !== 'running') return -1
- if (a.status !== 'running' && b.status === 'running') return 1
- return b.updatedAt - a.updatedAt
- }),
- [sessionState?.backgroundAgentTasks],
- )
const tokenUsage = sessionState?.tokenUsage ?? { input_tokens: 0, output_tokens: 0 }
const session = sessions.find((s) => s.id === activeTabId)
@@ -534,10 +363,10 @@ export function ActiveSession() {
className={
showWorkspacePanel
? 'flex w-full items-center border-b border-[var(--color-border)]/70 px-4 py-3'
- : 'mx-auto flex w-full max-w-[860px] items-center border-b border-outline-variant/10 px-8 py-3'
+ : 'w-full border-b border-outline-variant/10 px-4 py-3'
}
>
-
+
)}
- {activeGoal && (
-
- )}
-
-
>
)}
diff --git a/desktop/src/stores/chatStore.test.ts b/desktop/src/stores/chatStore.test.ts
index 80046372..91769291 100644
--- a/desktop/src/stores/chatStore.test.ts
+++ b/desktop/src/stores/chatStore.test.ts
@@ -373,6 +373,94 @@ describe('chatStore history mapping', () => {
})
})
+ it('uses transcript terminal events to repair stale live goal and background task state', async () => {
+ vi.mocked(sessionsApi.getMessages).mockResolvedValueOnce({
+ messages: [
+ {
+ id: 'goal-command',
+ type: 'system',
+ timestamp: '2026-04-06T00:00:00.000Z',
+ content: '/goal\nship the smoke test',
+ },
+ {
+ id: 'goal-output',
+ type: 'system',
+ timestamp: '2026-04-06T00:00:01.000Z',
+ content: 'Goal set: ship the smoke test',
+ },
+ {
+ id: 'goal-complete',
+ type: 'system',
+ timestamp: '2026-04-06T00:00:02.000Z',
+ content: 'Goal marked complete.',
+ },
+ ],
+ taskNotifications: [
+ {
+ taskId: 'agent-task-1',
+ toolUseId: 'agent-tool-1',
+ status: 'completed',
+ summary: 'Agent completed',
+ timestamp: '2026-04-06T00:00:03.000Z',
+ },
+ ],
+ })
+
+ useChatStore.setState({
+ sessions: {
+ [TEST_SESSION_ID]: makeSession({
+ messages: [{ id: 'visible-message', type: 'assistant_text', content: 'already rendered', timestamp: 1 }],
+ activeGoal: {
+ action: 'created',
+ status: 'active',
+ objective: 'ship the smoke test',
+ updatedAt: 1,
+ },
+ backgroundAgentTasks: {
+ 'agent-tool-1': {
+ taskId: 'agent-tool-1',
+ toolUseId: 'agent-tool-1',
+ status: 'running',
+ taskType: 'local_agent',
+ description: 'Review app',
+ startedAt: 1,
+ updatedAt: 2,
+ },
+ },
+ }),
+ },
+ })
+
+ await useChatStore.getState().loadHistory(TEST_SESSION_ID)
+
+ const session = useChatStore.getState().sessions[TEST_SESSION_ID]
+ expect(session?.messages).toMatchObject([
+ { id: 'visible-message', type: 'assistant_text', content: 'already rendered' },
+ {
+ type: 'background_task',
+ task: {
+ taskId: 'agent-task-1',
+ toolUseId: 'agent-tool-1',
+ status: 'completed',
+ summary: 'Agent completed',
+ },
+ },
+ ])
+ expect(session?.activeGoal).toMatchObject({
+ action: 'completed',
+ status: 'complete',
+ objective: 'ship the smoke test',
+ })
+ expect(session?.backgroundAgentTasks?.['agent-tool-1']).toBeUndefined()
+ expect(session?.backgroundAgentTasks?.['agent-task-1']).toMatchObject({
+ taskId: 'agent-task-1',
+ toolUseId: 'agent-tool-1',
+ status: 'completed',
+ description: 'Review app',
+ summary: 'Agent completed',
+ })
+ })
+
it('merges consecutive assistant text blocks when restoring transcript history', () => {
const messages: MessageEntry[] = [
{
@@ -1129,6 +1217,9 @@ describe('chatStore history mapping', () => {
})
it('tracks background agent task lifecycle events for desktop visibility', () => {
+ vi.useFakeTimers()
+ vi.setSystemTime(new Date('2026-04-06T00:00:01.000Z'))
+
useChatStore.setState({
sessions: {
[TEST_SESSION_ID]: makeSession({
@@ -1137,6 +1228,8 @@ describe('chatStore history mapping', () => {
},
})
+ vi.setSystemTime(new Date('2026-04-06T00:00:02.000Z'))
+
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'system_notification',
subtype: 'task_started',
@@ -1157,6 +1250,20 @@ describe('chatStore history mapping', () => {
taskType: 'local_agent',
prompt: 'Run E2E verification',
})
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([
+ {
+ type: 'background_task',
+ task: {
+ taskId: 'agent-task-1',
+ status: 'running',
+ description: 'Verify the todo app',
+ },
+ },
+ ])
+ const insertedTaskTimestamp = useChatStore.getState().sessions[TEST_SESSION_ID]?.messages[0]?.timestamp
+ expect(insertedTaskTimestamp).toBe(new Date('2026-04-06T00:00:02.000Z').getTime())
+
+ vi.setSystemTime(new Date('2026-04-06T00:00:03.000Z'))
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'system_notification',
@@ -1185,6 +1292,18 @@ describe('chatStore history mapping', () => {
durationMs: 45000,
},
})
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toHaveLength(1)
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages[0]).toMatchObject({
+ type: 'background_task',
+ task: {
+ taskId: 'agent-task-1',
+ status: 'running',
+ summary: 'Running Playwright checks',
+ },
+ })
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages[0]?.timestamp).toBe(insertedTaskTimestamp)
+
+ vi.setSystemTime(new Date('2026-04-06T00:00:04.000Z'))
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'system_notification',
@@ -1218,6 +1337,17 @@ describe('chatStore history mapping', () => {
summary: 'Found and fixed localStorage corruption.',
outputFile: '/tmp/agent-output.txt',
})
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toHaveLength(1)
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages[0]).toMatchObject({
+ type: 'background_task',
+ task: {
+ taskId: 'agent-task-1',
+ status: 'completed',
+ summary: 'Found and fixed localStorage corruption.',
+ },
+ })
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages[0]?.timestamp).toBe(insertedTaskTimestamp)
+ vi.useRealTimers()
})
it('clears local desktop chat state when the server confirms /clear', () => {
diff --git a/desktop/src/stores/chatStore.ts b/desktop/src/stores/chatStore.ts
index 153e661e..8f89eb6d 100644
--- a/desktop/src/stores/chatStore.ts
+++ b/desktop/src/stores/chatStore.ts
@@ -219,6 +219,41 @@ function appendAssistantTextMessage(
]
}
+function upsertBackgroundTaskMessage(
+ messages: UIMessage[],
+ task: BackgroundAgentTask,
+ timestamp: number,
+): UIMessage[] {
+ const existingIndex = messages.findIndex((message) =>
+ message.type === 'background_task' &&
+ (message.task.taskId === task.taskId ||
+ (task.toolUseId && message.task.toolUseId === task.toolUseId)))
+ if (existingIndex === -1) {
+ return [...messages, {
+ id: `background-task-${task.taskId}`,
+ type: 'background_task',
+ task,
+ timestamp,
+ }]
+ }
+
+ return messages.map((message, index) =>
+ index === existingIndex && message.type === 'background_task'
+ ? { ...message, task: { ...message.task, ...task }, timestamp: message.timestamp || timestamp }
+ : message)
+}
+
+function mergeBackgroundTaskMessages(
+ messages: UIMessage[],
+ tasks: Record,
+): UIMessage[] {
+ const merged = Object.values(tasks).reduce(
+ (current, task) => upsertBackgroundTaskMessage(current, task, task.updatedAt),
+ messages,
+ )
+ return [...merged].sort((a, b) => a.timestamp - b.timestamp)
+}
+
function normalizeMemoryEventFiles(data: unknown): MemoryEventFile[] {
if (!data || typeof data !== 'object') return []
const writtenPaths = (data as { writtenPaths?: unknown }).writtenPaths
@@ -278,14 +313,16 @@ function updateSessionIn(
async function fetchAndMapSessionHistory(sessionId: string) {
const { messages, taskNotifications } = await sessionsApi.getMessages(sessionId)
const uiMessages = mapHistoryMessagesToUiMessages(messages)
+ const restoredNotifications = {
+ ...reconstructAgentNotifications(messages),
+ ...agentNotificationRecordFromList(taskNotifications ?? []),
+ }
return {
rawMessages: messages,
uiMessages,
activeGoal: deriveActiveGoalFromMessages(uiMessages),
- restoredNotifications: {
- ...reconstructAgentNotifications(messages),
- ...agentNotificationRecordFromList(taskNotifications ?? []),
- },
+ restoredNotifications,
+ restoredBackgroundTasks: backgroundTaskRecordFromNotifications(Object.values(restoredNotifications)),
lastTodos: extractLastTodoWriteFromHistory(messages),
hasMessagesAfterTaskCompletion: hasUserMessagesAfterTaskCompletion(messages),
}
@@ -540,16 +577,32 @@ export const useChatStore = create((set, get) => ({
uiMessages,
activeGoal,
restoredNotifications,
+ restoredBackgroundTasks,
lastTodos,
hasMessagesAfterTaskCompletion,
} = await fetchAndMapSessionHistory(sessionId)
set((state) => {
const session = state.sessions[sessionId]
- if (!session || session.messages.length > 0) return state
+ if (!session) return state
+ if (session.messages.length > 0) {
+ return { sessions: updateSessionIn(state.sessions, sessionId, (s) => ({
+ activeGoal: activeGoal ?? s.activeGoal ?? null,
+ agentTaskNotifications: { ...s.agentTaskNotifications, ...restoredNotifications },
+ backgroundAgentTasks: mergeBackgroundAgentTaskRecords(
+ s.backgroundAgentTasks ?? {},
+ restoredBackgroundTasks,
+ ),
+ messages: mergeBackgroundTaskMessages(s.messages, restoredBackgroundTasks),
+ })) }
+ }
return { sessions: updateSessionIn(state.sessions, sessionId, (s) => ({
- messages: uiMessages,
+ messages: mergeBackgroundTaskMessages(uiMessages, restoredBackgroundTasks),
activeGoal,
agentTaskNotifications: { ...s.agentTaskNotifications, ...restoredNotifications },
+ backgroundAgentTasks: mergeBackgroundAgentTaskRecords(
+ s.backgroundAgentTasks ?? {},
+ restoredBackgroundTasks,
+ ),
})) }
})
if (lastTodos && lastTodos.length > 0) {
@@ -572,6 +625,7 @@ export const useChatStore = create((set, get) => ({
uiMessages,
activeGoal,
restoredNotifications,
+ restoredBackgroundTasks,
lastTodos,
hasMessagesAfterTaskCompletion,
} = await fetchAndMapSessionHistory(sessionId)
@@ -582,9 +636,10 @@ export const useChatStore = create((set, get) => ({
if (session.elapsedTimer) clearInterval(session.elapsedTimer)
return {
sessions: updateSessionIn(state.sessions, sessionId, () => ({
- messages: uiMessages,
+ messages: mergeBackgroundTaskMessages(uiMessages, restoredBackgroundTasks),
activeGoal,
agentTaskNotifications: restoredNotifications,
+ backgroundAgentTasks: restoredBackgroundTasks,
chatState: 'idle',
activeThinkingId: null,
activeToolUseId: null,
@@ -998,13 +1053,18 @@ export const useChatStore = create((set, get) => ({
const taskEvent = normalizeBackgroundAgentTaskEvent(msg.data, msg.subtype)
if (taskEvent) {
const now = Date.now()
- update((session) => ({
- backgroundAgentTasks: upsertBackgroundAgentTask(
+ update((session) => {
+ const backgroundAgentTasks = upsertBackgroundAgentTask(
session.backgroundAgentTasks ?? {},
taskEvent,
now,
- ),
- }))
+ )
+ const task = backgroundAgentTasks[taskEvent.taskId]
+ return {
+ backgroundAgentTasks,
+ ...(task ? { messages: upsertBackgroundTaskMessage(session.messages, task, now) } : {}),
+ }
+ })
}
}
if (msg.subtype === 'task_notification' && msg.data && typeof msg.data === 'object') {
@@ -1017,31 +1077,36 @@ export const useChatStore = create((set, get) => ({
const taskStatus = data.status
if (taskEvent) {
const now = Date.now()
- update((session) => ({
- backgroundAgentTasks: upsertBackgroundAgentTask(
+ update((session) => {
+ const backgroundAgentTasks = upsertBackgroundAgentTask(
session.backgroundAgentTasks ?? {},
taskEvent,
now,
- ),
- agentTaskNotifications: {
- ...session.agentTaskNotifications,
- ...(toolUseId &&
- (taskStatus === 'completed' ||
- taskStatus === 'failed' ||
- taskStatus === 'stopped')
- ? {
- [toolUseId]: {
- taskId: taskEvent.taskId,
- toolUseId,
- status: taskStatus,
- summary: taskEvent.summary,
- outputFile: taskEvent.outputFile,
- usage: taskEvent.usage,
- },
- }
- : {}),
- },
- }))
+ )
+ const task = backgroundAgentTasks[taskEvent.taskId]
+ return {
+ backgroundAgentTasks,
+ ...(task ? { messages: upsertBackgroundTaskMessage(session.messages, task, now) } : {}),
+ agentTaskNotifications: {
+ ...session.agentTaskNotifications,
+ ...(toolUseId &&
+ (taskStatus === 'completed' ||
+ taskStatus === 'failed' ||
+ taskStatus === 'stopped')
+ ? {
+ [toolUseId]: {
+ taskId: taskEvent.taskId,
+ toolUseId,
+ status: taskStatus,
+ summary: taskEvent.summary,
+ outputFile: taskEvent.outputFile,
+ usage: taskEvent.usage,
+ },
+ }
+ : {}),
+ },
+ }
+ })
}
}
break
@@ -1190,9 +1255,19 @@ function upsertBackgroundAgentTask(
event: Partial & Pick,
now: number,
): Record {
- const existing = current[event.taskId]
+ const existingKey = current[event.taskId]
+ ? event.taskId
+ : event.toolUseId
+ ? Object.keys(current).find((key) =>
+ key === event.toolUseId || current[key]?.toolUseId === event.toolUseId)
+ : undefined
+ const existing = existingKey ? current[existingKey] : undefined
+ const next = { ...current }
+ if (existingKey && existingKey !== event.taskId) {
+ delete next[existingKey]
+ }
return {
- ...current,
+ ...next,
[event.taskId]: {
taskId: event.taskId,
toolUseId: event.toolUseId ?? existing?.toolUseId,
@@ -1361,6 +1436,33 @@ function agentNotificationRecordFromList(
)
}
+function backgroundTaskRecordFromNotifications(
+ notifications: AgentTaskNotification[],
+): Record {
+ return notifications.reduce>((tasks, notification) => {
+ const parsedTimestamp = notification.timestamp ? new Date(notification.timestamp).getTime() : NaN
+ const now = Number.isFinite(parsedTimestamp) ? parsedTimestamp : Date.now()
+ return upsertBackgroundAgentTask(tasks, {
+ taskId: notification.taskId,
+ toolUseId: notification.toolUseId,
+ status: notification.status,
+ summary: notification.summary,
+ outputFile: notification.outputFile,
+ usage: notification.usage,
+ }, now)
+ }, {})
+}
+
+function mergeBackgroundAgentTaskRecords(
+ current: Record,
+ restored: Record,
+): Record {
+ return Object.values(restored).reduce(
+ (tasks, task) => upsertBackgroundAgentTask(tasks, task, task.updatedAt),
+ current,
+ )
+}
+
const TEAMMATE_CONTENT_REGEX = /]*>\n?([\s\S]*?)\n?<\/teammate-message>/g
function extractVisibleTeammateMessageContents(text: string): string[] {
diff --git a/desktop/src/types/chat.ts b/desktop/src/types/chat.ts
index 947503e8..cb8b24b6 100644
--- a/desktop/src/types/chat.ts
+++ b/desktop/src/types/chat.ts
@@ -157,6 +157,7 @@ export type AgentTaskNotification = {
summary?: string
outputFile?: string
usage?: BackgroundAgentTaskUsage
+ timestamp?: string
}
export type BackgroundAgentTaskUsage = {
@@ -215,6 +216,7 @@ export type UIMessage =
| { id: string; type: 'thinking'; content: string; timestamp: number }
| { id: string; type: 'tool_use'; toolName: string; toolUseId: string; input: unknown; timestamp: number; parentToolUseId?: string }
| { id: string; type: 'tool_result'; toolUseId: string; content: unknown; isError: boolean; timestamp: number; parentToolUseId?: string }
+ | { id: string; type: 'background_task'; task: BackgroundAgentTask; timestamp: number }
| { id: string; type: 'system'; content: string; timestamp: number }
| {
id: string
diff --git a/src/server/__tests__/sessions.test.ts b/src/server/__tests__/sessions.test.ts
index 866e86bb..ffd0eee9 100644
--- a/src/server/__tests__/sessions.test.ts
+++ b/src/server/__tests__/sessions.test.ts
@@ -873,6 +873,7 @@ describe('SessionService', () => {
toolUseId: 'toolu_bg',
status: 'completed',
summary: 'Background command completed',
+ timestamp: '2026-01-01T00:01:00.000Z',
},
])
})
@@ -1706,6 +1707,7 @@ describe('Sessions API', () => {
status: 'failed',
summary: 'Background command failed & stopped',
outputFile: 'C:\\Temp\\bg.output',
+ timestamp: expect.any(String),
},
])
})
diff --git a/src/server/services/sessionService.ts b/src/server/services/sessionService.ts
index e617b888..0e651d75 100644
--- a/src/server/services/sessionService.ts
+++ b/src/server/services/sessionService.ts
@@ -90,6 +90,7 @@ export type SessionTaskNotification = {
status: 'completed' | 'failed' | 'stopped'
summary?: string
outputFile?: string
+ timestamp?: string
}
export type TranscriptUsageSnapshot = {
@@ -512,7 +513,10 @@ export class SessionService {
return match?.[1] ? this.decodeXmlText(match[1].trim()) : undefined
}
- private parseTaskNotificationContent(content: unknown): SessionTaskNotification | null {
+ private parseTaskNotificationContent(
+ content: unknown,
+ timestamp?: string,
+ ): SessionTaskNotification | null {
const xml = this.extractTextBlocks(content)
.map((text) => this.extractTaskNotificationXml(text))
.find((value): value is string => value !== null)
@@ -536,6 +540,7 @@ export class SessionService {
status,
...(summary ? { summary } : {}),
...(outputFile ? { outputFile } : {}),
+ ...(timestamp ? { timestamp } : {}),
}
}
@@ -1835,7 +1840,10 @@ export class SessionService {
const notifications: SessionTaskNotification[] = []
for (const entry of entries) {
if (entry.message?.role !== 'user') continue
- const notification = this.parseTaskNotificationContent(entry.message.content)
+ const notification = this.parseTaskNotificationContent(
+ entry.message.content,
+ entry.timestamp,
+ )
if (notification) notifications.push(notification)
}
return notifications