From c5c93c502ee520d333085764aaf97f985ecd156f Mon Sep 17 00:00:00 2001 From: Sathvik Gilakamsetty Date: Thu, 28 May 2026 23:34:23 +0000 Subject: [PATCH] feat(events): live domain event stream log panel (#2653) Co-authored-by: sanil-23 Co-authored-by: Claude --- .../settings/panels/DeveloperOptionsPanel.tsx | 16 + .../settings/panels/EventLogPanel.tsx | 326 ++++++++++++++++++ .../panels/__tests__/EventLogPanel.test.tsx | 180 ++++++++++ app/src/lib/i18n/chunks/ar-5.ts | 23 ++ app/src/lib/i18n/chunks/bn-5.ts | 23 ++ app/src/lib/i18n/chunks/de-5.ts | 23 ++ app/src/lib/i18n/chunks/en-5.ts | 23 ++ app/src/lib/i18n/chunks/es-5.ts | 23 ++ app/src/lib/i18n/chunks/fr-5.ts | 23 ++ app/src/lib/i18n/chunks/hi-5.ts | 23 ++ app/src/lib/i18n/chunks/id-5.ts | 23 ++ app/src/lib/i18n/chunks/it-5.ts | 23 ++ app/src/lib/i18n/chunks/ko-5.ts | 23 ++ app/src/lib/i18n/chunks/pt-5.ts | 23 ++ app/src/lib/i18n/chunks/ru-5.ts | 23 ++ app/src/lib/i18n/chunks/zh-CN-5.ts | 23 ++ app/src/lib/i18n/en.ts | 23 ++ app/src/pages/Settings.tsx | 2 + src/core/event_bus/events.rs | 98 ++++++ src/core/jsonrpc.rs | 96 ++++++ src/openhuman/config/schema/dashboard.rs | 58 +++- src/openhuman/config/schema/types.rs | 4 +- 22 files changed, 1097 insertions(+), 5 deletions(-) create mode 100644 app/src/components/settings/panels/EventLogPanel.tsx create mode 100644 app/src/components/settings/panels/__tests__/EventLogPanel.test.tsx diff --git a/app/src/components/settings/panels/DeveloperOptionsPanel.tsx b/app/src/components/settings/panels/DeveloperOptionsPanel.tsx index d7b3375b3..3529af612 100644 --- a/app/src/components/settings/panels/DeveloperOptionsPanel.tsx +++ b/app/src/components/settings/panels/DeveloperOptionsPanel.tsx @@ -138,6 +138,22 @@ const developerItems = [ ), }, + { + id: 'event-log', + titleKey: 'settings.developerMenu.eventLog.title', + descriptionKey: 'settings.developerMenu.eventLog.desc', + route: 'event-log', + icon: ( + + + + ), + }, { id: 'tool-policy-diagnostics', titleKey: 'devOptions.diagnostics', diff --git a/app/src/components/settings/panels/EventLogPanel.tsx b/app/src/components/settings/panels/EventLogPanel.tsx new file mode 100644 index 000000000..95ec140a5 --- /dev/null +++ b/app/src/components/settings/panels/EventLogPanel.tsx @@ -0,0 +1,326 @@ +import { useEffect, useRef, useState } from 'react'; + +import { useT } from '../../../lib/i18n/I18nContext'; +import { getCoreHttpBaseUrl, getCoreRpcToken } from '../../../services/coreRpcClient'; +import SettingsHeader from '../components/SettingsHeader'; +import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; + +interface EventEntry { + id: number; + domain: string; + event: string; + agent: string; + timestamp: string; +} + +const DOMAIN_BADGE_KEYS: Record = { + tool: 'settings.developerMenu.eventLog.badge.tool', + agent: 'settings.developerMenu.eventLog.badge.agent', + system: 'settings.developerMenu.eventLog.badge.info', + memory: 'settings.developerMenu.eventLog.badge.mem', + channel: 'settings.developerMenu.eventLog.badge.chan', + cron: 'settings.developerMenu.eventLog.badge.cron', + webhook: 'settings.developerMenu.eventLog.badge.hook', + approval: 'settings.developerMenu.eventLog.badge.warn', + skill: 'settings.developerMenu.eventLog.badge.skill', + composio: 'settings.developerMenu.eventLog.badge.comp', + mcp_client: 'settings.developerMenu.eventLog.badge.mcp', +}; + +const DOMAIN_BADGE_COLORS: Record = { + tool: { bg: 'bg-blue-500/20', text: 'text-blue-400' }, + agent: { bg: 'bg-green-500/20', text: 'text-green-400' }, + system: { bg: 'bg-slate-500/20', text: 'text-slate-400' }, + memory: { bg: 'bg-purple-500/20', text: 'text-purple-400' }, + channel: { bg: 'bg-cyan-500/20', text: 'text-cyan-400' }, + cron: { bg: 'bg-orange-500/20', text: 'text-orange-400' }, + webhook: { bg: 'bg-indigo-500/20', text: 'text-indigo-400' }, + approval: { bg: 'bg-amber-500/20', text: 'text-amber-400' }, + skill: { bg: 'bg-teal-500/20', text: 'text-teal-400' }, + composio: { bg: 'bg-pink-500/20', text: 'text-pink-400' }, + mcp_client: { bg: 'bg-violet-500/20', text: 'text-violet-400' }, +}; + +const MAX_ENTRIES = 200; +const RECONNECT_DELAY_MS = 3000; + +const EventLogPanel = () => { + const { t } = useT(); + const { navigateBack, breadcrumbs } = useSettingsNavigation(); + const [entries, setEntries] = useState([]); + const [isLive, setIsLive] = useState(false); + const [filterType, setFilterType] = useState(''); + const [filterText, setFilterText] = useState(''); + const [autoScroll, setAutoScroll] = useState(true); + const containerRef = useRef(null); + const idRef = useRef(0); + const controllerRef = useRef(null); + const reconnectRef = useRef | null>(null); + const unmountedRef = useRef(false); + const maxEntriesRef = useRef(MAX_ENTRIES); + const newEntriesRef = useRef<'top' | 'bottom'>('top'); + + const connectRef = useRef<(() => Promise) | null>(null); + + const connect = async () => { + if (unmountedRef.current) return; + try { + const [baseUrl, token] = await Promise.all([getCoreHttpBaseUrl(), getCoreRpcToken()]); + if (!token) { + setIsLive(false); + return; + } + + const url = `${baseUrl}/events/domain`; + const controller = new AbortController(); + controllerRef.current = controller; + + const response = await fetch(url, { + headers: { Authorization: `Bearer ${token}` }, + signal: controller.signal, + }); + + if (!response.ok || !response.body) { + setIsLive(false); + return; + } + + setIsLive(true); + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + if (line.startsWith('event:')) { + const eventType = line.slice(6).trim(); + if (eventType === 'config') { + // Next data: line is config — handled below + continue; + } + } + if (line.startsWith('data:')) { + const jsonStr = line.slice(5).trim(); + if (!jsonStr) continue; + try { + const data = JSON.parse(jsonStr); + // Config message from server + if (data.max_entries !== undefined) { + maxEntriesRef.current = data.max_entries; + if (data.new_entries === 'top' || data.new_entries === 'bottom') { + newEntriesRef.current = data.new_entries; + } + continue; + } + const entry: EventEntry = { + id: ++idRef.current, + domain: data.domain || 'unknown', + event: data.event || '', + agent: data.agent || '', + timestamp: data.timestamp || '', + }; + setEntries(prev => { + const next = newEntriesRef.current === 'top' ? [entry, ...prev] : [...prev, entry]; + return next.length > maxEntriesRef.current + ? newEntriesRef.current === 'top' + ? next.slice(0, maxEntriesRef.current) + : next.slice(-maxEntriesRef.current) + : next; + }); + } catch { + // skip malformed + } + } + } + } + setIsLive(false); + } catch { + setIsLive(false); + } finally { + controllerRef.current = null; + // Auto-reconnect unless unmounted + if (!unmountedRef.current) { + reconnectRef.current = setTimeout(() => void connectRef.current?.(), RECONNECT_DELAY_MS); + } + } + }; + + connectRef.current = connect; + + useEffect(() => { + unmountedRef.current = false; + void connectRef.current?.(); + return () => { + unmountedRef.current = true; + controllerRef.current?.abort(); + controllerRef.current = null; + if (reconnectRef.current) { + clearTimeout(reconnectRef.current); + reconnectRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (autoScroll && containerRef.current) { + const el = containerRef.current; + el.scrollTop = newEntriesRef.current === 'top' ? 0 : el.scrollHeight; + } + }, [entries, autoScroll]); + + const handleScroll = () => { + const el = containerRef.current; + if (!el) return; + const atAnchor = + newEntriesRef.current === 'top' + ? el.scrollTop < 10 + : el.scrollHeight - el.scrollTop - el.clientHeight < 10; + setAutoScroll(atAnchor); + }; + + const filteredEntries = entries.filter(e => { + if (filterType && e.domain !== filterType) return false; + if (filterText) { + const q = filterText.toLowerCase(); + if (!e.event.toLowerCase().includes(q) && !e.agent.toLowerCase().includes(q)) return false; + } + return true; + }); + + const exportLog = () => { + const blob = new Blob([filteredEntries.map(e => JSON.stringify(e)).join('\n')], { + type: 'application/x-ndjson', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `event-log-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.ndjson`; + a.click(); + URL.revokeObjectURL(url); + }; + + const domains = [...new Set(entries.map(e => e.domain))].sort(); + + return ( +
+ + +
+ {/* Status bar */} +
+ + setFilterText(e.target.value)} + /> + + + {filteredEntries.length} {t('settings.developerMenu.eventLog.events')} ·{' '} + + {isLive + ? t('settings.developerMenu.eventLog.live') + : t('settings.developerMenu.eventLog.disconnected')} + + +
+ + {/* Jump to latest */} + {!autoScroll && ( + + )} + + {/* Event stream */} +
+
+ {filteredEntries.length === 0 && ( +

+ {isLive + ? t('settings.developerMenu.eventLog.waiting') + : t('settings.developerMenu.eventLog.notConnected')} +

+ )} + {filteredEntries.map(entry => { + const colors = DOMAIN_BADGE_COLORS[entry.domain] || { + bg: 'bg-stone-500/20', + text: 'text-stone-400', + }; + return ( +
+ + {entry.timestamp} + + + {DOMAIN_BADGE_KEYS[entry.domain] + ? t(DOMAIN_BADGE_KEYS[entry.domain]) + : entry.domain.toUpperCase()} + + {entry.agent && ( + + {entry.agent} + + )} + + {entry.event} + +
+ ); + })} +
+
+
+
+ ); +}; + +export default EventLogPanel; diff --git a/app/src/components/settings/panels/__tests__/EventLogPanel.test.tsx b/app/src/components/settings/panels/__tests__/EventLogPanel.test.tsx new file mode 100644 index 000000000..3a68f4fc5 --- /dev/null +++ b/app/src/components/settings/panels/__tests__/EventLogPanel.test.tsx @@ -0,0 +1,180 @@ +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { renderWithProviders } from '../../../../test/test-utils'; +import EventLogPanel from '../EventLogPanel'; + +vi.mock('../../../../services/coreRpcClient', () => ({ + getCoreHttpBaseUrl: vi.fn().mockResolvedValue('http://localhost:9999'), + getCoreRpcToken: vi.fn().mockResolvedValue('test-token'), +})); + +vi.mock('../../hooks/useSettingsNavigation', () => ({ + useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }), +})); + +vi.mock('../../../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (k: string) => k }) })); + +function mockFetchSSE(events: Array<{ domain: string; event: string }>) { + const lines = events.map(e => `data:${JSON.stringify({ ...e, timestamp: '12:00:00' })}\n`); + const body = lines.join(''); + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(body)); + controller.close(); + }, + }); + global.fetch = vi.fn().mockResolvedValue({ ok: true, body: stream }); +} + +describe('EventLogPanel', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders the panel with header and filter controls', () => { + global.fetch = vi.fn().mockResolvedValue({ ok: false, body: null }); + renderWithProviders(); + expect(screen.getByTestId('event-log-panel')).toBeTruthy(); + expect(screen.getByText('settings.developerMenu.eventLog.allTypes')).toBeTruthy(); + }); + + it('shows waiting message when connected but no events', async () => { + const stream = new ReadableStream({ + start() { + // never enqueue — stays open + }, + }); + global.fetch = vi.fn().mockResolvedValue({ ok: true, body: stream }); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('settings.developerMenu.eventLog.waiting')).toBeTruthy(); + }); + }); + + it('renders events from SSE stream with domain badges', async () => { + mockFetchSSE([ + { domain: 'tool', event: 'ToolExecuted' }, + { domain: 'agent', event: 'AgentStarted' }, + ]); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('ToolExecuted')).toBeTruthy(); + expect(screen.getByText('AgentStarted')).toBeTruthy(); + }); + + expect(screen.getByText('settings.developerMenu.eventLog.badge.tool')).toBeTruthy(); + expect(screen.getByText('settings.developerMenu.eventLog.badge.agent')).toBeTruthy(); + }); + + it('shows disconnected state when fetch fails', async () => { + global.fetch = vi.fn().mockRejectedValue(new Error('network')); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('settings.developerMenu.eventLog.disconnected')).toBeTruthy(); + }); + }); + + it('filters events by domain type', async () => { + mockFetchSSE([ + { domain: 'tool', event: 'ToolA' }, + { domain: 'agent', event: 'AgentB' }, + ]); + const { container } = renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('ToolA')).toBeTruthy(); + }); + + const select = container.querySelector('select')!; + fireEvent.change(select, { target: { value: 'tool' } }); + + await waitFor(() => { + expect(screen.queryByText('AgentB')).toBeNull(); + }); + expect(screen.getByText('ToolA')).toBeTruthy(); + }); + + it('shows not connected when token is missing', async () => { + const { getCoreRpcToken } = await import('../../../../services/coreRpcClient'); + vi.mocked(getCoreRpcToken).mockResolvedValueOnce(null as unknown as string); + global.fetch = vi.fn(); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('settings.developerMenu.eventLog.notConnected')).toBeTruthy(); + }); + expect(global.fetch).not.toHaveBeenCalled(); + }); + + it('exports filtered events as ndjson', async () => { + mockFetchSSE([{ domain: 'tool', event: 'ToolExport' }]); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('ToolExport')).toBeTruthy(); + }); + + const createObjectURL = vi.fn().mockReturnValue('blob:test'); + const revokeObjectURL = vi.fn(); + global.URL.createObjectURL = createObjectURL; + global.URL.revokeObjectURL = revokeObjectURL; + + const downloadBtn = screen.getByText('settings.developerMenu.eventLog.download'); + fireEvent.click(downloadBtn); + + expect(createObjectURL).toHaveBeenCalled(); + expect(revokeObjectURL).toHaveBeenCalledWith('blob:test'); + }); + + it('filters events by text input', async () => { + mockFetchSSE([ + { domain: 'tool', event: 'ToolMatch' }, + { domain: 'tool', event: 'Other' }, + ]); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('ToolMatch')).toBeTruthy(); + }); + + const input = screen.getByPlaceholderText('settings.developerMenu.eventLog.filterAgent'); + fireEvent.change(input, { target: { value: 'Match' } }); + + await waitFor(() => { + expect(screen.queryByText('Other')).toBeNull(); + }); + }); + + it('renders unknown domain as uppercase text', async () => { + mockFetchSSE([{ domain: 'custom_thing', event: 'Evt' }]); + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('CUSTOM_THING')).toBeTruthy(); + }); + }); + + it('handles scroll and shows jump-to-latest button', async () => { + mockFetchSSE([{ domain: 'tool', event: 'ScrollTest' }]); + const { container } = renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText('ScrollTest')).toBeTruthy(); + }); + + const scrollDiv = container.querySelector('.max-h-\\[60vh\\]')!; + Object.defineProperty(scrollDiv, 'scrollTop', { value: 100, writable: true }); + fireEvent.scroll(scrollDiv); + + await waitFor(() => { + expect(screen.getByText('settings.developerMenu.eventLog.jumpToLatest')).toBeTruthy(); + }); + + fireEvent.click(screen.getByText('settings.developerMenu.eventLog.jumpToLatest')); + }); +}); diff --git a/app/src/lib/i18n/chunks/ar-5.ts b/app/src/lib/i18n/chunks/ar-5.ts index 1d57ab957..3de9f5ab1 100644 --- a/app/src/lib/i18n/chunks/ar-5.ts +++ b/app/src/lib/i18n/chunks/ar-5.ts @@ -225,6 +225,29 @@ const ar5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'خطافات الويب', 'settings.developerMenu.webhooks.desc': 'فحص تسجيلات خطافات الويب وقت التشغيل وسجلات الطلبات الملتقطة', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'الذكاء', 'settings.developerMenu.intelligence.desc': 'مساحة عمل الذاكرة، ومحرك اللاوعي، والأحلام، والإعدادات', diff --git a/app/src/lib/i18n/chunks/bn-5.ts b/app/src/lib/i18n/chunks/bn-5.ts index 77c38e82b..8c235f757 100644 --- a/app/src/lib/i18n/chunks/bn-5.ts +++ b/app/src/lib/i18n/chunks/bn-5.ts @@ -231,6 +231,29 @@ const bn5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'ওয়েবহুক', 'settings.developerMenu.webhooks.desc': 'রানটাইম ওয়েবহুক রেজিস্ট্রেশন এবং ধরা পড়া রিকোয়েস্ট লগ পরিদর্শন করুন', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'ইন্টেলিজেন্স', 'settings.developerMenu.intelligence.desc': 'মেমরি ওয়ার্কস্পেস, সাবকনশাস ইঞ্জিন, ড্রিমস এবং সেটিংস', diff --git a/app/src/lib/i18n/chunks/de-5.ts b/app/src/lib/i18n/chunks/de-5.ts index 60fba5daa..675b50651 100644 --- a/app/src/lib/i18n/chunks/de-5.ts +++ b/app/src/lib/i18n/chunks/de-5.ts @@ -239,6 +239,29 @@ const de5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhooks', 'settings.developerMenu.webhooks.desc': 'Prüfe Laufzeit-Webhook-Registrierungen und erfasste Anforderungsprotokolle', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Intelligenz', 'settings.developerMenu.intelligence.desc': 'Gedächtnisarbeitsbereich, Unterbewusstseinsmotor, Träume und Einstellungen', diff --git a/app/src/lib/i18n/chunks/en-5.ts b/app/src/lib/i18n/chunks/en-5.ts index 28c7d3544..954252891 100644 --- a/app/src/lib/i18n/chunks/en-5.ts +++ b/app/src/lib/i18n/chunks/en-5.ts @@ -232,6 +232,29 @@ const en5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhooks', 'settings.developerMenu.webhooks.desc': 'Inspect runtime webhook registrations and captured request logs', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Intelligence', 'settings.developerMenu.intelligence.desc': 'Memory workspace, subconscious engine, dreams, and settings', diff --git a/app/src/lib/i18n/chunks/es-5.ts b/app/src/lib/i18n/chunks/es-5.ts index bdefbe22b..636dee86b 100644 --- a/app/src/lib/i18n/chunks/es-5.ts +++ b/app/src/lib/i18n/chunks/es-5.ts @@ -234,6 +234,29 @@ const es5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Ganchos web', 'settings.developerMenu.webhooks.desc': 'Inspecciona registros de webhooks en tiempo de ejecución y solicitudes capturadas', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Inteligencia', 'settings.developerMenu.intelligence.desc': 'Espacio de trabajo de memoria, motor subconsciente, sueños y ajustes', diff --git a/app/src/lib/i18n/chunks/fr-5.ts b/app/src/lib/i18n/chunks/fr-5.ts index 67011fcdc..6dafa597c 100644 --- a/app/src/lib/i18n/chunks/fr-5.ts +++ b/app/src/lib/i18n/chunks/fr-5.ts @@ -236,6 +236,29 @@ const fr5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhooks', 'settings.developerMenu.webhooks.desc': "Inspecter les enregistrements de webhooks d'exécution et les journaux de requêtes capturés", + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Intelligence', 'settings.developerMenu.intelligence.desc': 'Espace mémoire, moteur subconscient, rêves et paramètres', diff --git a/app/src/lib/i18n/chunks/hi-5.ts b/app/src/lib/i18n/chunks/hi-5.ts index 0ebb70319..998cc9401 100644 --- a/app/src/lib/i18n/chunks/hi-5.ts +++ b/app/src/lib/i18n/chunks/hi-5.ts @@ -231,6 +231,29 @@ const hi5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'वेबहुक्स', 'settings.developerMenu.webhooks.desc': 'रनटाइम वेबहुक रजिस्ट्रेशन और कैप्चर किए गए अनुरोध लॉग देखें', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'इंटेलिजेंस', 'settings.developerMenu.intelligence.desc': 'मेमोरी वर्कस्पेस, सबकॉन्शस इंजन, ड्रीम्स, और सेटिंग्स', diff --git a/app/src/lib/i18n/chunks/id-5.ts b/app/src/lib/i18n/chunks/id-5.ts index ffbf267a4..aa3f966e0 100644 --- a/app/src/lib/i18n/chunks/id-5.ts +++ b/app/src/lib/i18n/chunks/id-5.ts @@ -231,6 +231,29 @@ const id5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhook', 'settings.developerMenu.webhooks.desc': 'Periksa pendaftaran webhook runtime dan log permintaan yang tertangkap', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Kecerdasan', 'settings.developerMenu.intelligence.desc': 'Ruang kerja memori, mesin bawah sadar, mimpi, dan pengaturan', diff --git a/app/src/lib/i18n/chunks/it-5.ts b/app/src/lib/i18n/chunks/it-5.ts index 999c69c4f..16caab001 100644 --- a/app/src/lib/i18n/chunks/it-5.ts +++ b/app/src/lib/i18n/chunks/it-5.ts @@ -234,6 +234,29 @@ const it5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhook', 'settings.developerMenu.webhooks.desc': 'Ispeziona registrazioni webhook di runtime e log delle richieste catturate', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Intelligenza', 'settings.developerMenu.intelligence.desc': 'Area di lavoro memoria, motore subconscio, sogni e impostazioni', diff --git a/app/src/lib/i18n/chunks/ko-5.ts b/app/src/lib/i18n/chunks/ko-5.ts index 0be7a598c..1d31e2322 100644 --- a/app/src/lib/i18n/chunks/ko-5.ts +++ b/app/src/lib/i18n/chunks/ko-5.ts @@ -562,6 +562,29 @@ const ko5: TranslationMap = { 'settings.developerMenu.composio.desc': '라우팅 모드, 통합 트리거 및 트리거 기록 아카이브.', 'settings.developerMenu.webhooks.title': '웹훅', 'settings.developerMenu.webhooks.desc': '런타임 웹훅 등록 및 캡처된 요청 로그 검사', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': '인텔리전스', 'settings.developerMenu.intelligence.desc': '메모리 작업 공간, 잠재의식 엔진, 드림 및 설정', 'settings.developerMenu.notificationRouting.title': '알림 라우팅', diff --git a/app/src/lib/i18n/chunks/pt-5.ts b/app/src/lib/i18n/chunks/pt-5.ts index 41a5c36ea..9033e9230 100644 --- a/app/src/lib/i18n/chunks/pt-5.ts +++ b/app/src/lib/i18n/chunks/pt-5.ts @@ -235,6 +235,29 @@ const pt5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhooks', 'settings.developerMenu.webhooks.desc': 'Inspecione registros de webhooks em tempo de execução e logs de solicitações capturadas', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Inteligência', 'settings.developerMenu.intelligence.desc': 'Espaço de trabalho de memória, motor subconsciente, sonhos e configurações', diff --git a/app/src/lib/i18n/chunks/ru-5.ts b/app/src/lib/i18n/chunks/ru-5.ts index 239751e4f..746eb9464 100644 --- a/app/src/lib/i18n/chunks/ru-5.ts +++ b/app/src/lib/i18n/chunks/ru-5.ts @@ -232,6 +232,29 @@ const ru5: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Вебхуки', 'settings.developerMenu.webhooks.desc': 'Проверяйте регистрации runtime-вебхуков и журналы захваченных запросов', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Интеллект', 'settings.developerMenu.intelligence.desc': 'Рабочая область памяти, подсознательный движок, сны и настройки', diff --git a/app/src/lib/i18n/chunks/zh-CN-5.ts b/app/src/lib/i18n/chunks/zh-CN-5.ts index 23a5c1dff..933823fbe 100644 --- a/app/src/lib/i18n/chunks/zh-CN-5.ts +++ b/app/src/lib/i18n/chunks/zh-CN-5.ts @@ -218,6 +218,29 @@ const zhCN5: TranslationMap = { 'settings.developerMenu.localModelDebug.desc': 'Ollama 配置、资源下载、模型测试和诊断', 'settings.developerMenu.webhooks.title': 'Webhook', 'settings.developerMenu.webhooks.desc': '检查运行时 Webhook 注册和捕获的请求日志', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': '智能', 'settings.developerMenu.intelligence.desc': '记忆工作区、潜意识引擎、梦境和设置', 'settings.developerMenu.notificationRouting.title': '通知路由', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index b54f8f9ec..c11cc27e1 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -3262,6 +3262,29 @@ const en: TranslationMap = { 'settings.developerMenu.webhooks.title': 'Webhooks', 'settings.developerMenu.webhooks.desc': 'Inspect runtime webhook registrations and captured request logs', + 'settings.developerMenu.eventLog.title': 'Event Log', + 'settings.developerMenu.eventLog.desc': + 'Live colour-coded stream of all agent, tool, and system events', + 'settings.developerMenu.eventLog.allTypes': 'All types', + 'settings.developerMenu.eventLog.filterAgent': 'Filter...', + 'settings.developerMenu.eventLog.download': 'Download', + 'settings.developerMenu.eventLog.events': 'events', + 'settings.developerMenu.eventLog.live': 'Live', + 'settings.developerMenu.eventLog.disconnected': 'Disconnected', + 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', + 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', + 'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest', + 'settings.developerMenu.eventLog.badge.tool': 'TOOL', + 'settings.developerMenu.eventLog.badge.agent': 'AGENT', + 'settings.developerMenu.eventLog.badge.info': 'INFO', + 'settings.developerMenu.eventLog.badge.mem': 'MEM', + 'settings.developerMenu.eventLog.badge.chan': 'CHAN', + 'settings.developerMenu.eventLog.badge.cron': 'CRON', + 'settings.developerMenu.eventLog.badge.hook': 'HOOK', + 'settings.developerMenu.eventLog.badge.warn': 'WARN', + 'settings.developerMenu.eventLog.badge.skill': 'SKILL', + 'settings.developerMenu.eventLog.badge.comp': 'COMP', + 'settings.developerMenu.eventLog.badge.mcp': 'MCP', 'settings.developerMenu.intelligence.title': 'Intelligence', 'settings.developerMenu.intelligence.desc': 'Memory workspace, subconscious engine, dreams, and settings', diff --git a/app/src/pages/Settings.tsx b/app/src/pages/Settings.tsx index 74f600a63..7492e4def 100644 --- a/app/src/pages/Settings.tsx +++ b/app/src/pages/Settings.tsx @@ -20,6 +20,7 @@ import DeveloperOptionsPanel from '../components/settings/panels/DeveloperOption import DevicesComingSoonPanel from '../components/settings/panels/DevicesComingSoonPanel'; import DevWorkflowPanel from '../components/settings/panels/DevWorkflowPanel'; import EmbeddingsPanel from '../components/settings/panels/EmbeddingsPanel'; +import EventLogPanel from '../components/settings/panels/EventLogPanel'; import HeartbeatPanel from '../components/settings/panels/HeartbeatPanel'; import LedgerUsagePanel from '../components/settings/panels/LedgerUsagePanel'; import LocalModelDebugPanel from '../components/settings/panels/LocalModelDebugPanel'; @@ -467,6 +468,7 @@ const Settings = () => { )} /> )} /> )} /> + )} /> , { maxWidthClass: 'max-w-4xl' })} diff --git a/src/core/event_bus/events.rs b/src/core/event_bus/events.rs index a94d634c9..a11a56771 100644 --- a/src/core/event_bus/events.rs +++ b/src/core/event_bus/events.rs @@ -728,6 +728,104 @@ impl DomainEvent { | Self::McpSetupSecretRequested { .. } => "mcp_client", } } + + /// Stable variant name without payload (avoids Debug format coupling). + pub fn variant_name(&self) -> &'static str { + match self { + Self::AgentTurnStarted { .. } => "AgentTurnStarted", + Self::AgentTurnCompleted { .. } => "AgentTurnCompleted", + Self::AgentError { .. } => "AgentError", + Self::SubagentSpawned { .. } => "SubagentSpawned", + Self::SubagentCompleted { .. } => "SubagentCompleted", + Self::SubagentFailed { .. } => "SubagentFailed", + Self::MemoryStored { .. } => "MemoryStored", + Self::MemoryRecalled { .. } => "MemoryRecalled", + Self::MemorySyncRequested { .. } => "MemorySyncRequested", + Self::MemorySyncStageChanged { .. } => "MemorySyncStageChanged", + Self::MemoryIngestionStarted { .. } => "MemoryIngestionStarted", + Self::MemoryIngestionCompleted { .. } => "MemoryIngestionCompleted", + Self::DocumentCanonicalized { .. } => "DocumentCanonicalized", + Self::CacheRebuilt { .. } => "CacheRebuilt", + Self::ChannelInboundMessage { .. } => "ChannelInboundMessage", + Self::ChannelMessageReceived { .. } => "ChannelMessageReceived", + Self::ChannelMessageProcessed { .. } => "ChannelMessageProcessed", + Self::ChannelReactionReceived { .. } => "ChannelReactionReceived", + Self::ChannelReactionSent { .. } => "ChannelReactionSent", + Self::ChannelConnected { .. } => "ChannelConnected", + Self::ChannelDisconnected { .. } => "ChannelDisconnected", + Self::CronJobTriggered { .. } => "CronJobTriggered", + Self::CronJobCompleted { .. } => "CronJobCompleted", + Self::CronDeliveryRequested { .. } => "CronDeliveryRequested", + Self::ProactiveMessageRequested { .. } => "ProactiveMessageRequested", + Self::SkillLoaded { .. } => "SkillLoaded", + Self::SkillStopped { .. } => "SkillStopped", + Self::SkillStartFailed { .. } => "SkillStartFailed", + Self::SkillExecuted { .. } => "SkillExecuted", + Self::ToolExecutionStarted { .. } => "ToolExecutionStarted", + Self::ToolExecutionCompleted { .. } => "ToolExecutionCompleted", + Self::WebhookIncomingRequest { .. } => "WebhookIncomingRequest", + Self::WebhookReceived { .. } => "WebhookReceived", + Self::WebhookRegistered { .. } => "WebhookRegistered", + Self::WebhookUnregistered { .. } => "WebhookUnregistered", + Self::WebhookProcessed { .. } => "WebhookProcessed", + Self::ComposioTriggerReceived { .. } => "ComposioTriggerReceived", + Self::ComposioConnectionCreated { .. } => "ComposioConnectionCreated", + Self::ComposioConnectionDeleted { .. } => "ComposioConnectionDeleted", + Self::ComposioActionExecuted { .. } => "ComposioActionExecuted", + Self::ComposioConfigChanged { .. } => "ComposioConfigChanged", + Self::TriggerEvaluated { .. } => "TriggerEvaluated", + Self::TriggerEscalated { .. } => "TriggerEscalated", + Self::TriggerEscalationFailed { .. } => "TriggerEscalationFailed", + Self::TreeSummarizerHourCompleted { .. } => "TreeSummarizerHourCompleted", + Self::TreeSummarizerPropagated { .. } => "TreeSummarizerPropagated", + Self::TreeSummarizerRebuildCompleted { .. } => "TreeSummarizerRebuildCompleted", + Self::NotificationIngested { .. } => "NotificationIngested", + Self::NotificationTriaged { .. } => "NotificationTriaged", + Self::DevicePaired { .. } => "DevicePaired", + Self::DeviceRevoked { .. } => "DeviceRevoked", + Self::DevicePeerOnline { .. } => "DevicePeerOnline", + Self::DevicePeerOffline { .. } => "DevicePeerOffline", + Self::DeviceTunnelFrame { .. } => "DeviceTunnelFrame", + Self::DeviceTunnelRegistered { .. } => "DeviceTunnelRegistered", + Self::CompanionSessionStarted { .. } => "CompanionSessionStarted", + Self::CompanionStateChanged { .. } => "CompanionStateChanged", + Self::CompanionSessionEnded { .. } => "CompanionSessionEnded", + Self::SystemStartup { .. } => "SystemStartup", + Self::SystemShutdown { .. } => "SystemShutdown", + Self::SystemRestartRequested { .. } => "SystemRestartRequested", + Self::SystemShutdownRequested { .. } => "SystemShutdownRequested", + Self::AutonomyConfigChanged => "AutonomyConfigChanged", + Self::HealthChanged { .. } => "HealthChanged", + Self::HealthRestarted { .. } => "HealthRestarted", + Self::SessionExpired { .. } => "SessionExpired", + Self::ApprovalRequested { .. } => "ApprovalRequested", + Self::ApprovalDecided { .. } => "ApprovalDecided", + Self::McpServerInstalled { .. } => "McpServerInstalled", + Self::McpServerConnected { .. } => "McpServerConnected", + Self::McpServerDisconnected { .. } => "McpServerDisconnected", + Self::McpClientToolExecuted { .. } => "McpClientToolExecuted", + Self::McpSetupSecretRequested { .. } => "McpSetupSecretRequested", + Self::EmbeddingModelUnhealthy { .. } => "EmbeddingModelUnhealthy", + } + } + + /// Best-effort agent/session hint for display (not all events carry one). + pub fn agent_hint(&self) -> Option<&str> { + match self { + Self::AgentTurnStarted { session_id, .. } + | Self::AgentTurnCompleted { session_id, .. } + | Self::AgentError { session_id, .. } => Some(session_id.as_str()), + Self::SubagentSpawned { agent_id, .. } + | Self::SubagentCompleted { agent_id, .. } + | Self::SubagentFailed { agent_id, .. } => Some(agent_id.as_str()), + Self::ChannelMessageReceived { channel, .. } + | Self::ChannelConnected { channel, .. } + | Self::ChannelDisconnected { channel, .. } => Some(channel.as_str()), + Self::ToolExecutionStarted { tool_name, .. } + | Self::ToolExecutionCompleted { tool_name, .. } => Some(tool_name.as_str()), + _ => None, + } + } } #[cfg(test)] diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index fd006343e..14aa5f936 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -862,6 +862,7 @@ pub fn build_core_http_router(socketio_enabled: bool) -> Router { .route("/schema", get(schema_handler)) .route("/events", get(events_handler)) .route("/events/webhooks", get(webhook_events_handler)) + .route("/events/domain", get(domain_events_handler)) .route("/rpc", post(rpc_handler)) .route("/ws/dictation", get(dictation_ws_handler)) .route("/auth", get(desktop_auth_handler)) @@ -1186,6 +1187,101 @@ async fn webhook_events_handler() -> Response { .into_response() } +/// SSE endpoint streaming DomainEvent bus events for the live event log panel. +/// +/// Requires bearer auth. Streams all domain events as JSON with event type +/// set to the domain name (agent, tool, memory, etc.). +async fn domain_events_handler(headers: axum::http::HeaderMap) -> Response { + let bearer = headers + .get(header::AUTHORIZATION) + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.strip_prefix("Bearer ")) + .map(str::trim) + .filter(|s| !s.is_empty()); + let bearer_ok = bearer + .map(crate::core::auth::verify_bearer_token) + .unwrap_or(false); + + if !bearer_ok { + log::warn!("[events/domain] reject subscribe: missing or invalid bearer token"); + return ( + StatusCode::UNAUTHORIZED, + Json(json!({ + "ok": false, + "error": "unauthorized", + "message": "Bearer token required for domain event stream" + })), + ) + .into_response(); + } + + // Read dashboard config for event stream settings. + let es_cfg = crate::openhuman::config::rpc::load_config_with_timeout() + .await + .map(|c| c.dashboard.event_stream) + .unwrap_or_default(); + + if !es_cfg.enabled { + return ( + StatusCode::NOT_FOUND, + Json(json!({ "ok": false, "error": "event stream disabled by config" })), + ) + .into_response(); + } + + let bus = match crate::core::event_bus::global() { + Some(bus) => bus, + None => { + log::warn!("[events/domain] event bus not initialized"); + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(json!({ "ok": false, "error": "event bus not initialized" })), + ) + .into_response(); + } + }; + + log::debug!("[events/domain] client connected, streaming domain events"); + + // Send config as first SSE event so frontend can apply settings. + let config_event = Event::default().event("config").data( + serde_json::to_string(&json!({ + "max_entries": es_cfg.max_entries, + "new_entries": es_cfg.new_entries, + })) + .unwrap_or_default(), + ); + + let rx = bus.raw_receiver(); + let event_stream = tokio_stream::wrappers::BroadcastStream::new(rx).filter_map( + |item| -> Option> { + let event = match item { + Ok(ev) => ev, + Err(_) => return None, + }; + let domain = event.domain().to_string(); + let event_name = event.variant_name(); + let agent = event.agent_hint().unwrap_or("").to_string(); + let data = json!({ + "domain": domain, + "event": event_name, + "agent": agent, + "timestamp": chrono::Utc::now().format("%H:%M:%S").to_string(), + }); + let data_str = serde_json::to_string(&data).ok()?; + Some(Ok(Event::default().event(domain).data(data_str))) + }, + ); + + let config_stream = + futures::stream::once(async move { Ok::<_, std::convert::Infallible>(config_event) }); + let stream = config_stream.chain(event_stream); + + Sse::new(stream) + .keep_alive(KeepAlive::new().interval(std::time::Duration::from_secs(5))) + .into_response() +} + /// Handler for the root endpoint, returning server information and available endpoints. async fn root_handler() -> impl IntoResponse { let api_server = match crate::openhuman::config::Config::load_or_init().await { diff --git a/src/openhuman/config/schema/dashboard.rs b/src/openhuman/config/schema/dashboard.rs index 1efd55cf8..c8ebeee41 100644 --- a/src/openhuman/config/schema/dashboard.rs +++ b/src/openhuman/config/schema/dashboard.rs @@ -1,3 +1,5 @@ +//! Dashboard configuration (event stream, model health, future panels). + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -16,6 +18,7 @@ fn default_diagram_viewer_refresh_interval_seconds() -> u64 { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct DashboardConfig { + #[serde(default)] pub event_stream: EventStreamConfig, #[serde(default)] pub model_health: ModelHealthConfig, @@ -36,17 +39,36 @@ impl Default for DashboardConfig { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct EventStreamConfig { - #[serde(default = "default_es_enabled")] + /// Whether the live event stream endpoint is enabled. + #[serde(default = "default_enabled")] pub enabled: bool, + + /// Maximum number of entries the frontend should retain. + #[serde(default = "default_max_entries")] + pub max_entries: usize, + + /// Where new entries appear: "top" (newest first) or "bottom" (oldest first). + #[serde(default = "default_new_entries")] + pub new_entries: String, } -fn default_es_enabled() -> bool { +fn default_enabled() -> bool { true } +fn default_max_entries() -> usize { + 200 +} +fn default_new_entries() -> String { + "top".to_string() +} impl Default for EventStreamConfig { fn default() -> Self { - Self { enabled: true } + Self { + enabled: true, + max_entries: 200, + new_entries: "top".to_string(), + } } } @@ -112,6 +134,36 @@ impl Default for DiagramViewerConfig { mod tests { use super::*; + #[test] + fn defaults_match_issue_spec() { + let cfg = DashboardConfig::default(); + assert!(cfg.event_stream.enabled); + assert_eq!(cfg.event_stream.max_entries, 200); + assert_eq!(cfg.event_stream.new_entries, "top"); + } + + #[test] + fn deserialize_from_empty_json() { + let cfg: DashboardConfig = serde_json::from_value(serde_json::json!({})).unwrap(); + assert!(cfg.event_stream.enabled); + assert_eq!(cfg.event_stream.max_entries, 200); + } + + #[test] + fn deserialize_custom_values() { + let cfg: DashboardConfig = serde_json::from_value(serde_json::json!({ + "event_stream": { + "enabled": false, + "max_entries": 500, + "new_entries": "bottom" + } + })) + .unwrap(); + assert!(!cfg.event_stream.enabled); + assert_eq!(cfg.event_stream.max_entries, 500); + assert_eq!(cfg.event_stream.new_entries, "bottom"); + } + #[test] fn dashboard_config_defaults_enable_local_diagram_viewer() { let config = DashboardConfig::default(); diff --git a/src/openhuman/config/schema/types.rs b/src/openhuman/config/schema/types.rs index 1677d9f20..01b20bc97 100644 --- a/src/openhuman/config/schema/types.rs +++ b/src/openhuman/config/schema/types.rs @@ -87,10 +87,10 @@ pub struct Config { pub temperature_unsupported_models: Vec, #[serde(default)] - pub observability: ObservabilityConfig, + pub dashboard: DashboardConfig, #[serde(default)] - pub dashboard: DashboardConfig, + pub observability: ObservabilityConfig, #[serde(default)] pub autonomy: AutonomyConfig,