feat(events): live domain event stream log panel#2653
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an authenticated SSE backend endpoint at /events/domain and a new Event Log settings panel that streams, parses, filters, auto-scrolls, displays, and exports live domain events. ChangesEvent Log Dashboard Feature
Sequence Diagram(s)sequenceDiagram
participant SettingsUI
participant EventLogPanel
participant EventAPI
participant CoreEventBus
SettingsUI->>EventLogPanel: open panel / mount
EventLogPanel->>EventAPI: GET /events/domain (Bearer token)
EventAPI->>EventLogPanel: SSE stream (data: JSON lines, keep-alive)
loop on each DomainEvent
CoreEventBus->>EventAPI: DomainEvent
EventAPI->>EventLogPanel: data: {domain,event,timestamp}
EventLogPanel->>SettingsUI: render prepend entry, update filters/controls
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
app/src/components/settings/panels/EventLogPanel.tsx (1)
47-50: ⚡ Quick winRemove the unused
EventSourcepath to simplify ownership/cleanup.The panel streams via
fetch, so creating/closing/storingEventSourceis dead code and makes lifecycle behavior harder to follow.Proposed cleanup
- const eventSourceRef = useRef<EventSource | null>(null); @@ - const es = new EventSource(url); - - // EventSource doesn't support custom headers, so we use fetch-based SSE - es.close(); + // EventSource doesn't support custom headers, so we use fetch-based SSE @@ - eventSourceRef.current = es; return () => { controller.abort(); }; @@ return () => { disconnect?.(); - eventSourceRef.current?.close(); };Also applies to: 101-101, 110-110
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/EventLogPanel.tsx` around lines 47 - 50, Remove the dead EventSource path: delete the instantiation and immediate close of EventSource (the variable `es` created via `new EventSource(url)`) and any other unused `EventSource` usage in EventLogPanel (including the occurrences around lines referenced at 101 and 110) so the component only uses the fetch-based SSE implementation; ensure no leftover references to `es` remain and that ownership/cleanup logic is consolidated into the fetch/SSE cleanup handlers in the EventLogPanel component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/EventLogPanel.tsx`:
- Around line 13-25: The component renders hard-coded UI text (badges and
status/button/count labels) — update it to use the i18n hook and keys: replace
visible literals like "Live", "Disconnected", "events", "Jump to latest" and
every badge label in EVENT_TYPE_COLORS with translation keys consumed via useT()
(e.g., change EVENT_TYPE_COLORS.{...}.label to a key string like
"eventLog.badge.tool" and when rendering call t('eventLog.badge.tool')). Add
corresponding entries to the English i18n file (app/src/lib/i18n/en.ts) for each
new key, update the JSX props (label/aria-label/button text/count suffix) to
call t(...) instead of hard-coded text, and ensure any tests or snapshots that
assert on these strings are updated accordingly.
- Line 3: The import for useT in EventLogPanel is using the wrong relative path
and causes a build error; update the import statement that currently references
'../../lib/i18n/I18nContext' to the correct relative path
'../../../lib/i18n/I18nContext' so the useT symbol is resolved (modify the
import line near the top of EventLogPanel.tsx to import useT from
'../../../lib/i18n/I18nContext').
- Around line 41-112: connect() creates an AbortController and starts the fetch
but the useEffect that calls connect() ignores its cleanup; update connect (the
function in EventLogPanel.tsx) to return a cleanup function that aborts the
controller and closes the EventSource/reader, then call that cleanup from the
useEffect's return so the streaming read loop is aborted on unmount (i.e., have
useEffect capture the cleanup = connect() and return () => { cleanup(); } or
store the controller in a ref and abort it in useEffect cleanup); ensure you
still set/clear eventSourceRef.current and call controller.abort() to prevent
state updates after unmount.
In `@app/src/lib/i18n/en.ts`:
- Around line 2960-2967: The new i18n keys under settings.developerMenu.eventLog
(for example 'settings.developerMenu.eventLog.title', '.desc', '.allTypes',
'.filterAgent', '.download', '.waiting', '.notConnected') were added only to
app/src/lib/i18n/en.ts; add these same keys (with English values) into each i18n
chunk file app/src/lib/i18n/chunks/en-1.ts ... en-5.ts and then add the
corresponding keys to each non-English locale chunk file (keeping existing
translations or adding placeholder translations) so all locale chunks include
the settings.developerMenu.eventLog.* entries to satisfy the i18n coverage gate.
---
Nitpick comments:
In `@app/src/components/settings/panels/EventLogPanel.tsx`:
- Around line 47-50: Remove the dead EventSource path: delete the instantiation
and immediate close of EventSource (the variable `es` created via `new
EventSource(url)`) and any other unused `EventSource` usage in EventLogPanel
(including the occurrences around lines referenced at 101 and 110) so the
component only uses the fetch-based SSE implementation; ensure no leftover
references to `es` remain and that ownership/cleanup logic is consolidated into
the fetch/SSE cleanup handlers in the EventLogPanel component.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ed93a8f5-d588-4eb9-afe5-a98150822c38
📒 Files selected for processing (5)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/EventLogPanel.tsxapp/src/lib/i18n/en.tsapp/src/pages/Settings.tsxsrc/core/jsonrpc.rs
| '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 by agent...', | ||
| 'settings.developerMenu.eventLog.download': 'Download', | ||
| 'settings.developerMenu.eventLog.waiting': 'Waiting for events...', | ||
| 'settings.developerMenu.eventLog.notConnected': 'Not connected to core', |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify newly added event log keys exist across required locale files/chunks.
keys=(
"settings.developerMenu.eventLog.title"
"settings.developerMenu.eventLog.desc"
"settings.developerMenu.eventLog.allTypes"
"settings.developerMenu.eventLog.filterAgent"
"settings.developerMenu.eventLog.download"
"settings.developerMenu.eventLog.waiting"
"settings.developerMenu.eventLog.notConnected"
)
echo "== Checking English chunk files =="
fd -a '^en-[1-5]\.ts$' app/src/lib/i18n | sort
echo "== Checking all locale files/chunks for key presence =="
for k in "${keys[@]}"; do
echo "--- $k ---"
rg -n --fixed-strings "'$k'" app/src/lib/i18n || true
doneRepository: tinyhumansai/openhuman
Length of output: 1381
🏁 Script executed:
#!/bin/bash
set -euo pipefail
keys=(
"settings.developerMenu.eventLog.title"
"settings.developerMenu.eventLog.desc"
"settings.developerMenu.eventLog.allTypes"
"settings.developerMenu.eventLog.filterAgent"
"settings.developerMenu.eventLog.download"
"settings.developerMenu.eventLog.waiting"
"settings.developerMenu.eventLog.notConnected"
)
echo "== Search for eventLog in chunks =="
rg -n "settings\.developerMenu\.eventLog" app/src/lib/i18n/chunks || true
echo "== Search for each key (no quote dependency) in chunks =="
for k in "${keys[@]}"; do
echo "--- $k ---"
rg -n --fixed-strings "$k" app/src/lib/i18n/chunks || true
doneRepository: tinyhumansai/openhuman
Length of output: 502
Propagate settings.developerMenu.eventLog.* keys to all i18n chunk files
The new settings.developerMenu.eventLog keys exist only in app/src/lib/i18n/en.ts (lines 2960-2967) and are missing from app/src/lib/i18n/chunks/en-{1..5}.ts and the corresponding non-English locale chunk files. This will break the i18n coverage gate.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/i18n/en.ts` around lines 2960 - 2967, The new i18n keys under
settings.developerMenu.eventLog (for example
'settings.developerMenu.eventLog.title', '.desc', '.allTypes', '.filterAgent',
'.download', '.waiting', '.notConnected') were added only to
app/src/lib/i18n/en.ts; add these same keys (with English values) into each i18n
chunk file app/src/lib/i18n/chunks/en-1.ts ... en-5.ts and then add the
corresponding keys to each non-English locale chunk file (keeping existing
translations or adding placeholder translations) so all locale chunks include
the settings.developerMenu.eventLog.* entries to satisfy the i18n coverage gate.
8b04c54 to
5505f38
Compare
5505f38 to
5b81259
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/EventLogPanel.tsx`:
- Around line 30-31: The hard-coded MAX_ENTRIES and fixed newest-on-top behavior
in EventLogPanel should be driven by the app config keys
dashboard.event_stream.max_entries and dashboard.event_stream.new_entries:
update EventLogPanel to read those config values (or accept them as props)
instead of using the MAX_ENTRIES constant, use the configured max_entries to cap
the list length, and conditionally render/insert new events at the top when
new_entries is true (and at the bottom when false); replace references to
MAX_ENTRIES and the existing insertion/ordering logic (including the code around
the current newest-on-top handling at the area noted around lines 92-95) to
respect these config-driven settings.
- Around line 1-249: Prettier is failing for EventLogPanel.tsx; run the
project's Prettier formatter (or npm/yarn script such as "npm run format" /
"yarn format") and apply formatting to this file so it matches CI rules; ensure
you preserve exported symbol EventLogPanel and do not change logic in functions
like connect, exportLog, or constants DOMAIN_BADGE_KEYS and MAX_ENTRIES—only
reformat the file to satisfy prettier --check.
- Around line 71-104: The read loop for the SSE stream exits on EOF but never
clears the live flag, so call setIsLive(false) when the stream ends; inside the
async reader loop (the block using reader.read(), decoder, buffer and
idRef.current) setIsLive(false) right before/after breaking when done is true
(or move the setIsLive(false) into a finally/after-loop cleanup) so the
component's isLive state is set to disconnected when the stream completes or is
closed.
In `@app/src/pages/Settings.tsx`:
- Line 43: Run Prettier to format the Settings.tsx file to satisfy CI: run the
project's Prettier command (e.g., prettier --write) on the file and fix
formatting issues around the import for EventLogPanel and other locations noted
(approx. the Settings component top imports and the block around line 449).
Ensure the file compiles after formatting and commit the formatted file so
prettier --check passes in CI.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 97e5266e-bf94-474e-ab65-5735929bbfeb
📒 Files selected for processing (18)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/EventLogPanel.tsxapp/src/lib/i18n/chunks/ar-5.tsapp/src/lib/i18n/chunks/bn-5.tsapp/src/lib/i18n/chunks/de-5.tsapp/src/lib/i18n/chunks/en-5.tsapp/src/lib/i18n/chunks/es-5.tsapp/src/lib/i18n/chunks/fr-5.tsapp/src/lib/i18n/chunks/hi-5.tsapp/src/lib/i18n/chunks/id-5.tsapp/src/lib/i18n/chunks/it-5.tsapp/src/lib/i18n/chunks/ko-5.tsapp/src/lib/i18n/chunks/pt-5.tsapp/src/lib/i18n/chunks/ru-5.tsapp/src/lib/i18n/chunks/zh-CN-5.tsapp/src/lib/i18n/en.tsapp/src/pages/Settings.tsxsrc/core/jsonrpc.rs
✅ Files skipped from review due to trivial changes (5)
- app/src/lib/i18n/chunks/de-5.ts
- app/src/lib/i18n/chunks/ar-5.ts
- app/src/lib/i18n/chunks/id-5.ts
- app/src/lib/i18n/chunks/pt-5.ts
- app/src/lib/i18n/chunks/zh-CN-5.ts
| const MAX_ENTRIES = 200; | ||
|
|
There was a problem hiding this comment.
Entry cap/order are hard-coded, not config-driven.
This implementation fixes behavior to 200 entries and newest-on-top. The linked objective requires dashboard.event_stream.max_entries and dashboard.event_stream.new_entries to drive this behavior.
Also applies to: 92-95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/components/settings/panels/EventLogPanel.tsx` around lines 30 - 31,
The hard-coded MAX_ENTRIES and fixed newest-on-top behavior in EventLogPanel
should be driven by the app config keys dashboard.event_stream.max_entries and
dashboard.event_stream.new_entries: update EventLogPanel to read those config
values (or accept them as props) instead of using the MAX_ENTRIES constant, use
the configured max_entries to cap the list length, and conditionally
render/insert new events at the top when new_entries is true (and at the bottom
when false); replace references to MAX_ENTRIES and the existing
insertion/ordering logic (including the code around the current newest-on-top
handling at the area noted around lines 92-95) to respect these config-driven
settings.
| import VoiceDebugPanel from '../components/settings/panels/VoiceDebugPanel'; | ||
| import VoicePanel from '../components/settings/panels/VoicePanel'; | ||
| import WebhooksDebugPanel from '../components/settings/panels/WebhooksDebugPanel'; | ||
| import EventLogPanel from '../components/settings/panels/EventLogPanel'; |
There was a problem hiding this comment.
Run Prettier on this file to unblock CI.
prettier --check is currently failing for this file; please format it before merge (around Line 43 and Line 449 changes included).
Also applies to: 449-449
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/pages/Settings.tsx` at line 43, Run Prettier to format the
Settings.tsx file to satisfy CI: run the project's Prettier command (e.g.,
prettier --write) on the file and fix formatting issues around the import for
EventLogPanel and other locations noted (approx. the Settings component top
imports and the block around line 449). Ensure the file compiles after
formatting and commit the formatted file so prettier --check passes in CI.
SSE endpoint GET /events/domain streams DomainEvent bus. React panel at Settings > Developer > Event Log with colour-coded badges, type/agent filtering, auto-scroll, and ndjson export. Closes tinyhumansai#1849
5b81259 to
d5aedab
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/core/jsonrpc.rs (1)
1077-1083: ⚡ Quick winHarden event-name scrubbing for tuple-style debug output.
The current split logic misses
Variant(...)shapes, so payload text can leak intoevent_nameif tuple variants are introduced.🔧 Suggested tweak
- let event_name = variant - .split_once('{') - .or_else(|| variant.split_once(' ')) + let event_name = variant + .split_once('{') + .or_else(|| variant.split_once('(')) + .or_else(|| variant.split_once(' ')) .map(|(name, _)| name.trim()) .unwrap_or(&variant);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/jsonrpc.rs` around lines 1077 - 1083, The event-name extraction for the Debug-formatted `variant` (assigned to `variant` and used to create `event_name`) currently only splits on '{' and ' ' and therefore can leak payload text for tuple-style variants like `Variant(...)`; update the scrub logic to also split on '(' (i.e., find the first occurrence of any of '{', ' ', '('), then take the left part and trim it, so tuple payloads are stripped the same as struct-style payloads; change the code that computes `event_name` (the block that calls `split_once` on `variant`) to try these separators in a robust order and fall back to the whole `variant` if none match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/EventLogPanel.tsx`:
- Around line 125-128: The current filteredEntries logic uses filterText against
e.event but the UI intends an agent-name filter; update the predicate to check
the entry's agent name field instead of e.event — e.g. replace the condition `if
(filterText && !e.event.toLowerCase().includes(filterText.toLowerCase()))` with
a check against the agent property (for example `const agentName = e.agent ||
e.agentName || e.actor` and `if (filterText &&
!agentName?.toLowerCase().includes(filterText.toLowerCase())) return false;`) so
the filterText matches agent names case-insensitively while keeping the existing
filterType check and final return.
- Around line 105-111: The cleanup in EventLogPanel's useEffect currently calls
setIsLive(false) which triggers the react-hooks/set-state-in-effect warning;
remove setIsLive(false) from the cleanup and instead ensure isLive is cleared
inside the connection lifecycle: update the connect() implementation to
setIsLive(false) on abort/error/finally and also make abort() set isLive false
(e.g., when controllerRef.current?.abort() is invoked handle the abort signal or
promise rejection to call setIsLive(false)), or if you intentionally must change
state in cleanup add a single-line eslint-disable comment with a clear
justification referencing why setIsLive must run on unmount and target
react-hooks/set-state-in-effect.
---
Nitpick comments:
In `@src/core/jsonrpc.rs`:
- Around line 1077-1083: The event-name extraction for the Debug-formatted
`variant` (assigned to `variant` and used to create `event_name`) currently only
splits on '{' and ' ' and therefore can leak payload text for tuple-style
variants like `Variant(...)`; update the scrub logic to also split on '(' (i.e.,
find the first occurrence of any of '{', ' ', '('), then take the left part and
trim it, so tuple payloads are stripped the same as struct-style payloads;
change the code that computes `event_name` (the block that calls `split_once` on
`variant`) to try these separators in a robust order and fall back to the whole
`variant` if none match.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fabe830f-8085-4beb-972e-64c5ba074386
📒 Files selected for processing (18)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/EventLogPanel.tsxapp/src/lib/i18n/chunks/ar-5.tsapp/src/lib/i18n/chunks/bn-5.tsapp/src/lib/i18n/chunks/de-5.tsapp/src/lib/i18n/chunks/en-5.tsapp/src/lib/i18n/chunks/es-5.tsapp/src/lib/i18n/chunks/fr-5.tsapp/src/lib/i18n/chunks/hi-5.tsapp/src/lib/i18n/chunks/id-5.tsapp/src/lib/i18n/chunks/it-5.tsapp/src/lib/i18n/chunks/ko-5.tsapp/src/lib/i18n/chunks/pt-5.tsapp/src/lib/i18n/chunks/ru-5.tsapp/src/lib/i18n/chunks/zh-CN-5.tsapp/src/lib/i18n/en.tsapp/src/pages/Settings.tsxsrc/core/jsonrpc.rs
✅ Files skipped from review due to trivial changes (8)
- app/src/lib/i18n/chunks/bn-5.ts
- app/src/lib/i18n/chunks/it-5.ts
- app/src/lib/i18n/chunks/es-5.ts
- app/src/lib/i18n/chunks/pt-5.ts
- app/src/lib/i18n/chunks/ru-5.ts
- app/src/lib/i18n/chunks/de-5.ts
- app/src/lib/i18n/chunks/en-5.ts
- app/src/lib/i18n/en.ts
| const filteredEntries = entries.filter(e => { | ||
| if (filterType && e.domain !== filterType) return false; | ||
| if (filterText && !e.event.toLowerCase().includes(filterText.toLowerCase())) return false; | ||
| return true; |
There was a problem hiding this comment.
Filter logic does not match “agent” filter intent.
Line 127 filters filterText against e.event, but the UI key (settings.developerMenu.eventLog.filterAgent) indicates agent-name filtering. This misses the stated behavior for agent search.
Suggested direction
interface EventEntry {
id: number;
domain: string;
+ agent?: string;
event: string;
timestamp: string;
}
@@
- const entry: EventEntry = {
+ const entry: EventEntry = {
id: ++idRef.current,
domain: data.domain || 'unknown',
+ agent: data.agent || '',
event: data.event || '',
timestamp: data.timestamp || '',
};
@@
- if (filterText && !e.event.toLowerCase().includes(filterText.toLowerCase())) return false;
+ if (filterText && !e.agent?.toLowerCase().includes(filterText.toLowerCase())) return false;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const filteredEntries = entries.filter(e => { | |
| if (filterType && e.domain !== filterType) return false; | |
| if (filterText && !e.event.toLowerCase().includes(filterText.toLowerCase())) return false; | |
| return true; | |
| const filteredEntries = entries.filter(e => { | |
| if (filterType && e.domain !== filterType) return false; | |
| if (filterText && !e.agent?.toLowerCase().includes(filterText.toLowerCase())) return false; | |
| return true; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/components/settings/panels/EventLogPanel.tsx` around lines 125 - 128,
The current filteredEntries logic uses filterText against e.event but the UI
intends an agent-name filter; update the predicate to check the entry's agent
name field instead of e.event — e.g. replace the condition `if (filterText &&
!e.event.toLowerCase().includes(filterText.toLowerCase()))` with a check against
the agent property (for example `const agentName = e.agent || e.agentName ||
e.actor` and `if (filterText &&
!agentName?.toLowerCase().includes(filterText.toLowerCase())) return false;`) so
the filterText matches agent names case-insensitively while keeping the existing
filterType check and final return.
Summary
GET /events/domainstreaming all DomainEvent bus events as JSONProblem
Agent activity lives in log files or stdout. Operators cannot see what agents are doing in real time without tailing multiple streams. No unified, colour-coded, filterable feed exists inside the dashboard.
Solution
src/core/jsonrpc.rssubscribes to the existingEventBusviabus.raw_receiver(), serializes eachDomainEventwith domain, variant name, and timestampEventLogPanelcomponent using fetch-based SSE with bearer auth, 200-entry ring buffer, type/agent filters, auto-scroll logic, and ndjson downloadSubmission Checklist
Impact
Related
Closes #1849
Summary by CodeRabbit