Skip to content

Add AIOX Visual Observer dashboard and WebSocket server#599

Open
devoliverluccas wants to merge 8 commits intoSynkraAI:mainfrom
devoliverluccas:claude/aios-visual-observability-bfZ3z
Open

Add AIOX Visual Observer dashboard and WebSocket server#599
devoliverluccas wants to merge 8 commits intoSynkraAI:mainfrom
devoliverluccas:claude/aios-visual-observability-bfZ3z

Conversation

@devoliverluccas
Copy link
Copy Markdown

@devoliverluccas devoliverluccas commented Mar 13, 2026

Pull Request

📋 Description

Introduces the AIOX Visual Observer — a real-time web-based monitoring dashboard for tracking agent execution, pipeline progress, and system events. This includes:

  • dashboard.html: Single-file, self-contained dashboard UI with GitHub Dark theme
  • server.js: Native Node.js HTTP/WebSocket server (RFC 6455) that broadcasts events to connected clients
  • event-store.js: In-memory circular event buffer with derived state management

The observer receives events via HTTP POST from DashboardEmitter and Python hooks, maintains a 200-event circular buffer, watches bob-status.json for pipeline updates, and streams real-time data to browser clients via WebSocket.

🎯 AIOX Story Reference

Story ID: TBD
Story File: TBD
Sprint: TBD

Acceptance Criteria Addressed

  • AC ID: TBD

🔗 Related Issue

N/A

📦 Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🐛 Bug fix
  • 💥 Breaking change
  • 📚 Documentation update
  • 🔧 Refactoring
  • ⚡ Performance improvement
  • 🧪 Test update

🎯 Scope

  • Core framework (aiox-core/)
  • Squad (squads/)
  • Tools (tools/)
  • Documentation (docs/)
  • CI/CD (.github/)
  • Other: Observer (observer/)

📝 Changes Made

New Files

  1. observer/dashboard.html (828 lines)

    • Single-file HTML/CSS/JS dashboard with GitHub Dark theme
    • Real-time display of active agents, pipeline progress, event log, and metrics
    • WebSocket client with auto-reconnect and exponential backoff
    • Event filtering, auto-scroll toggle, and uptime tracking
    • Responsive grid layout (2-column on desktop, 1-column on mobile)
  2. observer/server.js (506 lines)

    • Native Node.js HTTP/WebSocket server (no external WS library required)
    • RFC 6455 WebSocket handshake and frame handling
    • HTTP endpoints: POST /events, GET /, GET /status, GET /events/recent, WS /ws
    • File watcher for bob-status.json using chokidar (graceful degradation if unavailable)
    • Broadcasts events to all connected clients in real-time
    • Serves dashboard.html and provides state snapshots to new clients
  3. observer/event-store.js (267 lines)

    • In-memory circular buffer (max 200 events) with no external dependencies
    • Derives current state from event stream (active agents, pipeline stage, metrics)
    • Supports event types: BobPhaseChange, BobAgentSpawned, BobAgentCompleted, AgentActivated, AgentDeactivated, SessionStart, SessionEnd
    • Calculates events-per-minute rate from last 60 seconds of timestamps
    • Provides state snapshots and recent event queries
  4. .synapse/.gitignore (3 lines)

    • Ignores SYNAPSE runtime data directories (sessions/, cache/)

Modified Files

  1. eslint.config.js
    • Added observer/** to ESLint ignore patterns (observer is a standalone runtime tool, not part of TS project)

🧪 Testing

  • No automated tests added (observer is a standalone monitoring tool with straightforward event handling and WebSocket logic)
  • Manual testing: Start server with node observer/server.js, open http://localhost:4001 in browser, POST events to /events endpoint
  • WebSocket connection, event broadcasting, and state derivation are synchronous and deterministic

📸 Screenshots (if applicable)

N/A (dashboard is interactive web UI; visual verification requires running the server

https://claude.ai/code/session_01LuDQ7x1o5tJ4G71LZvqGqQ

Summary by CodeRabbit

  • New Features

    • Visual Observer dashboard for real-time monitoring with WebSocket-driven agent/status, pipeline progress, active terminals, metrics, and searchable/filtered event logs.
    • In-memory observer event store and lightweight observer server to collect, persist, and broadcast events to the dashboard.
  • Chores

    • Marked the observer runtime in lint/config.
    • Added runtime data patterns to VCS ignore.
    • Updated install manifest metadata.

claude added 3 commits March 13, 2026 19:28
Adds observer/ — an isolated monitor server that receives events from
DashboardEmitter and Python hooks (port 4001) and broadcasts them to a
single-file HTML dashboard via native RFC 6455 WebSocket.

- observer/event-store.js: in-memory circular buffer (max 200 events),
  derives state from event stream (agents, phase, pipeline, metrics)
- observer/server.js: HTTP + WebSocket server with zero new dependencies;
  RFC 6455 handshake via Node.js crypto; chokidar watches bob-status.json
  and broadcasts status_update frames; silent failure on all observer errors
- observer/dashboard.html: single-file dark terminal dashboard; shows active
  agent, pipeline progress (6 stages), terminals, filterable event log;
  auto-reconnect with exponential backoff; no frameworks, no CDN, no build

Zero modifications to existing AIOX code. Observer is purely additive.

Usage: node observer/server.js  →  open http://localhost:4001

https://claude.ai/code/session_01LuDQ7x1o5tJ4G71LZvqGqQ
- event-store.js: DashboardEmitter._postEvent nests session_id/aiox_agent/
  aiox_story_id inside data{}, not at top-level — read from both locations
  for compatibility with Python hooks (which may send top-level fields)
- server.js: generate UUID for events received via POST (CLI emitter omits id)
- eslint.config.js: add observer/** to ignores — standalone runtime tool

https://claude.ai/code/session_01LuDQ7x1o5tJ4G71LZvqGqQ
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 13, 2026

@claude is attempting to deploy a commit to the Pedro Valério Lopez's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added area: agents Agent system related area: workflows Workflow system related squad mcp type: test Test coverage and quality area: core Core framework (.aios-core/core/) area: installer Installer and setup (packages/installer/) area: synapse SYNAPSE context engine area: cli CLI tools (bin/, packages/aios-pro-cli/) area: pro Pro features (pro/) area: health-check Health check system area: docs Documentation (docs/) area: devops CI/CD, GitHub Actions (.github/) labels Mar 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Walkthrough

Adds an Observer subsystem: a Node.js HTTP+WebSocket server, an in-memory event store, and a standalone dashboard HTML for real-time visualization. Also updates ESLint config for observer/** and adds Synapse runtime ignore patterns to .synapse/.gitignore.

Changes

Cohort / File(s) Summary
Gitignore
\.synapse/\.gitignore
Adds header # SYNAPSE runtime data (auto-generated) and ignore patterns: sessions/, cache/.
Lint config
eslint.config.js
Adds observerOverride flat-config for observer/** with sourceType: 'commonjs' and a restricted set of CommonJS/Node globals; isolates observer/** file-scoped languageOptions.
Observer — Dashboard
observer/dashboard.html
New dark-themed standalone HTML dashboard with in-page JS: WebSocket client to /ws, liveState handling, event log (bounded), filtering, auto-scroll, pipeline/agent/terminal UI, reconnection/backoff, uptime and metrics display.
Observer — Event Store
observer/event-store.js
New in-memory circular event store factory createEventStore() (max ~200 events) exposing: addEvent, setBobStatus, getRecentEvents, getState, setConnectedClients, reset; maintains derived serializable dashboard state and metrics.
Observer — Server
observer/server.js
New Node HTTP + WebSocket server: / serves dashboard, POST /events, GET /status, GET /events/recent, handles WebSocket upgrade at /ws with RFC6455 framing/masking, broadcasts event/status_update, integrates createEventStore(), optional chokidar watcher on .aiox/dashboard/bob-status.json, exports startServer, broadcast, store.
Install manifest
.aiox-core/install-manifest.yaml
Updated generated_at timestamp and one entity-registry entry hash/size; other entries unchanged.

Sequence Diagram

sequenceDiagram
    participant External as External System
    participant Server as Observer Server
    participant Store as Event Store
    participant Client as Browser Client

    External->>Server: POST /events (JSON)
    Server->>Store: addEvent(event)
    Note over Store: update circular buffer<br/>update derived dashboard state
    Server->>Server: broadcast(event) to WS clients
    Server->>Client: send event via WebSocket
    Client->>Client: update liveState and UI

    Client->>Server: WebSocket connect (/ws)
    Server->>Store: getState() & getRecentEvents()
    Server->>Client: send init + recent events
    Client->>Client: populate initial UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • Pedrovaleriolopez
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add AIOX Visual Observer dashboard and WebSocket server' directly and accurately summarizes the main change—introduction of observer tool with dashboard and server components.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to aiox-core! Thanks for your first pull request.

What happens next?

  1. Automated checks will run on your PR
  2. A maintainer will review your changes
  3. Once approved, we'll merge your contribution!

PR Checklist:

Thanks for contributing!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
observer/server.js (1)

43-43: Use the repo's absolute import style for the event store.

The relative ./event-store import breaks the JS/TS import rule for this repo. Please switch this to the project's absolute form so the new observer code follows the same convention. As per coding guidelines, "Use absolute imports instead of relative imports in all code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` at line 43, The import in observer/server.js uses a
relative path for createEventStore; replace the relative
require('./event-store') with the repo's absolute import form (use the project's
root-level/package absolute path for the event store, e.g.,
require('event-store') or the repo's designated absolute namespace) so
createEventStore is imported via the project's absolute import convention rather
than a relative path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eslint.config.js`:
- Around line 64-65: Remove the blanket ignore for 'observer/**' in
eslint.config.js so the observer files (e.g., observer/event-store.js) remain
linted; instead add an overrides entry targeting "files": ["observer/**"] that
sets appropriate env/parserOptions (CommonJS, ecmaVersion: 2022) and relaxes
only the specific rules you need changed (or turn off a small set like
'no-restricted-syntax' or rule X) rather than excluding the whole directory,
ensuring use-before-declaration and other core rules still run.

In `@observer/dashboard.html`:
- Around line 518-540: The metrics display is being overridden by the retained
log row count instead of using the canonical liveState.metrics; update
appendEventToLog (and any other places that set 'm-total' such as the blocks
around applyState and the sections at lines ~563-598 and ~617-624) to read and
render from liveState.metrics.total (falling back to 0) rather than using
logRows.length or local counters, and ensure any event-delta updates mutate
liveState.metrics.total so setText('m-total', ...) always reflects
liveState.metrics; keep setText('m-rate', ...) using
liveState.metrics.eventsPerMin similarly.
- Around line 480-486: The code is injecting untrusted values via row.innerHTML
(see terminals.forEach and other row-building blocks that interpolate t.agent,
t.pid, t.task, event.type, summary), which allows XSS; replace those innerHTML
constructions with createElement + textContent: create span elements for agent,
pid and task, set their className (e.g., 'terminal-agent', 'terminal-pid',
'terminal-task') and assign the untrusted values to span.textContent (or
properly escape/sanitize where necessary) and append them to row via
appendChild; apply the same change to the other similar block that builds rows
(the block that also uses event.type and summary) to ensure no direct HTML
interpolation of untrusted input.
- Around line 723-735: In the case 'status_update' handler, set
liveState.currentPhase from the incoming pipeline/current stage before calling
updateAgentCard(liveState) so the agent card uses the new phase; specifically,
after you assign liveState.pipeline.current (and call setText('agent-phase',
...)) assign liveState.currentPhase = liveState.pipeline.current || null (or
similar) before updateAgentCard(liveState) so the card no longer reverts to
stale/— values.

In `@observer/event-store.js`:
- Around line 90-102: The code reads nested fields from data (data.session_id,
data.aiox_agent, data.aiox_story_id) before data is declared, causing a
ReferenceError; fix by moving the declaration const data = event.data || {};
above the block that computes session_id, aiox_agent, and aiox_story_id (so the
fallbacks use the defined data), then keep the existing assignments to
state.sessionId, state.currentStory, and state.currentAgent in place; ensure you
update any related comments and retain support for both top-level and nested
shapes when modifying the block that references event and data.

In `@observer/server.js`:
- Around line 228-243: Server is currently exposed broadly: bind server to
localhost by default, remove wildcard CORS, and add origin/token checks for both
HTTP routes and WebSocket upgrades; in handleUpgrade (and any other upgrade
handlers), reject upgrades whose req.url is not '/ws' and validate
req.headers.origin and an auth token (e.g., Authorization or a custom header)
before calling wsHandshake or adding socket to wsClients; for HTTP endpoints
like POST /events and GET /status, enforce the same origin/token validation and
stop sending Access-Control-Allow-Origin: * (use specific origin or omit header
when origin invalid); ensure failures call socket.destroy() or send 401/403
responses and do not add clients to wsClients (references: handleUpgrade,
wsHandshake, wsClients, store.setConnectedClients, POST /events handler).
- Around line 308-320: readBody() currently buffers the entire request with no
limit; change it to enforce a configurable max size (e.g., maxBytes constant or
parameter) by tracking accumulated byte length inside the 'data' handler and
immediately rejecting with a specific error (e.g., a PayloadTooLargeError or
Error with code/name 'PayloadTooLarge') if the limit is exceeded, cleaning up
listeners and optionally destroying the socket; keep JSON.parse on 'end' as
before. Also update the caller/handler that awaits readBody() to catch that
specific error and return a 413 Payload Too Large response when seen. Use the
existing readBody function name and the request handler that calls it to locate
where to add the size check and the 413 mapping.

---

Nitpick comments:
In `@observer/server.js`:
- Line 43: The import in observer/server.js uses a relative path for
createEventStore; replace the relative require('./event-store') with the repo's
absolute import form (use the project's root-level/package absolute path for the
event store, e.g., require('event-store') or the repo's designated absolute
namespace) so createEventStore is imported via the project's absolute import
convention rather than a relative path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db4dd763-b421-4c2c-94ce-9957f804088d

📥 Commits

Reviewing files that changed from the base of the PR and between f74e3e7 and 928445c.

📒 Files selected for processing (5)
  • .synapse/.gitignore
  • eslint.config.js
  • observer/dashboard.html
  • observer/event-store.js
  • observer/server.js

Comment thread eslint.config.js Outdated
Comment thread observer/dashboard.html Outdated
Comment thread observer/dashboard.html Outdated
Comment thread observer/dashboard.html Outdated
Comment thread observer/event-store.js Outdated
Comment thread observer/server.js Outdated
Comment on lines +228 to +243
function handleUpgrade(req, socket) {
const key = req.headers['sec-websocket-key'];
if (!key) {
socket.destroy();
return;
}

try {
wsHandshake(socket, key);
} catch (_e) {
socket.destroy();
return;
}

wsClients.add(socket);
store.setConnectedClients(wsClients.size);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Lock down the HTTP and WebSocket endpoints.

This server listens on all interfaces, returns Access-Control-Allow-Origin: *, and accepts POST /events plus WS upgrades without any auth/origin checks. That lets any local webpage—or any host that can reach the port—inject fake events, read /status, or subscribe to the live feed. Bind to 127.0.0.1 by default, reject non-/ws upgrades, and require an origin/token check for both HTTP and WS.

Also applies to: 329-335, 367-384, 484-490

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` around lines 228 - 243, Server is currently exposed
broadly: bind server to localhost by default, remove wildcard CORS, and add
origin/token checks for both HTTP routes and WebSocket upgrades; in
handleUpgrade (and any other upgrade handlers), reject upgrades whose req.url is
not '/ws' and validate req.headers.origin and an auth token (e.g., Authorization
or a custom header) before calling wsHandshake or adding socket to wsClients;
for HTTP endpoints like POST /events and GET /status, enforce the same
origin/token validation and stop sending Access-Control-Allow-Origin: * (use
specific origin or omit header when origin invalid); ensure failures call
socket.destroy() or send 401/403 responses and do not add clients to wsClients
(references: handleUpgrade, wsHandshake, wsClients, store.setConnectedClients,
POST /events handler).

Comment thread observer/server.js Outdated
claude added 2 commits March 14, 2026 00:56
`data` was declared at line ~102 but referenced at lines ~93-95,
causing a ReferenceError (temporal dead zone) on every POST /events.
Events were stored in the circular buffer but state derivation
(currentPhase, currentAgent, currentStory) never updated, and the
server returned 400 Bad Request instead of {"ok":true}.

Move `const data = event.data || {}` before the context envelope
extraction block so all references resolve correctly.

https://claude.ai/code/session_01LuDQ7x1o5tJ4G71LZvqGqQ
Auto-generated updates from IDS hook (entity-registry lastVerified
timestamps + entityCount) and package-lock.json version sync to 5.0.3.

https://claude.ai/code/session_01LuDQ7x1o5tJ4G71LZvqGqQ
@nikolasdehor
Copy link
Copy Markdown
Contributor

Projeto ambicioso — dashboard de observabilidade em tempo real é algo que falta no ecossistema AIOX. Algumas observações:

  1. WebSocket nativo (RFC 6455): Parabéns por não adicionar dependência do ws. A implementação manual do handshake e framing é educativa e mantém o footprint mínimo. Porém, atenção com edge cases: fragmentação de frames, mensagens binárias, ping/pong keepalive. Vale adicionar pelo menos o ping/pong pra detectar conexões mortas.

  2. Circular buffer de 200 eventos: Design inteligente pra evitar memory leak. O createEventStore() com factory function ao invés de classe é consistente com o estilo funcional do projeto.

  3. Diretório observer/: Ele fica fora do .aiox-core/ — isso é intencional? O resto dos módulos core vive dentro de .aiox-core/core/. Talvez valha alinhar com os maintainers se observer é considerado "core" ou "tooling externo". Se for core, mover pra .aiox-core/core/observer/ manteria a convenção.

  4. Story reference TBD: Entendo que é WIP, mas antes do merge seria bom ter o story ID definido pra rastreabilidade.

  5. Segurança: O servidor HTTP aceita POST em /events sem autenticação. Em ambiente local tudo bem, mas se alguém expor a porta, qualquer processo pode injetar eventos falsos. Considerar um token simples via env var (AIOX_OBSERVER_TOKEN).

  6. chokidar graceful degradation: Boa abordagem com o try/catch — se não tiver instalado, só desabilita file-watching.

Feature muito útil pra debug de pipelines. Seria legal ver integração futura com o EventEmitter do core.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
observer/server.js (1)

229-249: ⚠️ Potential issue | 🔴 Critical

Bind the observer to loopback and require auth before accepting clients.

server.listen(port) exposes this process on every interface, while /events, /status, /events/recent, and /ws still accept unauthenticated requests. The localhost CORS header only affects browsers, so any host that can reach the port can still inject or read observer data. Default to 127.0.0.1/::1 and gate both HTTP and upgrade paths behind a shared token or strict origin check.

Also applies to: 395-426, 490-518

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` around lines 229 - 249, The server currently listens on
all interfaces and accepts unauthenticated HTTP and WebSocket requests; update
the listener and request handling so the observer binds to loopback and enforces
a shared token or strict origin check before accepting clients: change
server.listen(...) to bind to 127.0.0.1 and ::1, and in the HTTP routes that
serve /events, /status, /events/recent and in handleUpgrade (which uses
wsHandshake, wsClients, and store.setConnectedClients) validate a configured
secret token (e.g., from env) or verify the Origin header before
proceeding—reject and destroy the socket or respond 401 if the token/origin is
missing/invalid so both HTTP and websocket upgrade paths are gated by the same
auth check.
🧹 Nitpick comments (1)
observer/server.js (1)

43-43: Use the repo's absolute-import convention for event-store.

This new observer runtime is still importing event-store relatively. Please switch it to the established absolute import form so the module stays aligned with the repository standard.

As per coding guidelines, "Use absolute imports instead of relative imports in all code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` at line 43, Replace the relative require for the event
store with the repo's absolute-import convention: locate the import that uses
createEventStore (const { createEventStore } = require('./event-store')) and
change it to the project's absolute module path for event-store so it uses the
same import pattern as other files (keeping the createEventStore symbol
unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eslint.config.js`:
- Around line 3-4: The observer override currently uses files: ['observer/**']
which matches all assets; change the glob to target only JS files by updating
the observerOverride.files pattern (the observerOverride constant) to something
like ['observer/**/*.js','observer/**/*.cjs','observer/**/*.mjs'] or at minimum
['observer/**/*.js'] so the CommonJS Node.js-specific override applies only to
JavaScript source files and not static assets like observer/dashboard.html.

In `@observer/dashboard.html`:
- Around line 618-620: The footer metrics are rendered before the live event
delta is applied, causing the total and events/min to lag; in appendEventToLog()
call applyEventDelta() first to update liveState.metrics, then use
liveState.metrics.total and liveState.metrics.eventsPerMinute (or the
appropriate metrics field) when calling setText('m-total', ...) and
setText('log-count', ...); make the same change at the other occurrences (around
the setText calls at the other noted locations) so both total and events/min are
computed from liveState.metrics after the delta is applied.
- Around line 672-676: The "Active Terminals" list is only appended in the
BobAgentSpawned path (liveState.bobStatus.active_terminals and renderTerminals)
but not pruned when agents finish; update the BobAgentCompleted and
AgentDeactivated handlers to remove any entries from
liveState.bobStatus.active_terminals that match the completed/deactivated agent
(match by agent and pid), then call
renderTerminals(liveState.bobStatus.active_terminals) to re-render; ensure you
guard for liveState.bobStatus and initialize active_terminals as an array if
needed before manipulating it.
- Around line 726-734: The init handler currently appends recentEvents onto the
existing buffer, causing duplicates after reconnect; in the case 'init' block
(around liveState, applyState, recentEvents, appendEventToLog) either clear the
current UI log buffer before iterating (reset logRows / DOM rows) or filter
recentEvents to skip any event whose id already exists in the log (compare
event.id against existing log row ids) before calling appendEventToLog;
implement one of these fixes so reconnects replace or dedupe the last-50 events
instead of duplicating them.

In `@observer/server.js`:
- Around line 450-480: startStatusWatcher currently creates a persistent
chokidar watcher and discards the handle, causing the watcher to keep the
process alive after shutdown; modify startStatusWatcher to return the watcher
instance (the variable watcher) and update the server shutdown logic in
startServer (or the server 'close' handler) to call watcher.close() (or
watcher.unwatch()/close equivalent) when server.close() runs; also apply the
same change to the similar watcher creation at the other location referenced
(around the 520-522 block) so all persistent watchers are returned and
explicitly closed on server shutdown.

---

Duplicate comments:
In `@observer/server.js`:
- Around line 229-249: The server currently listens on all interfaces and
accepts unauthenticated HTTP and WebSocket requests; update the listener and
request handling so the observer binds to loopback and enforces a shared token
or strict origin check before accepting clients: change server.listen(...) to
bind to 127.0.0.1 and ::1, and in the HTTP routes that serve /events, /status,
/events/recent and in handleUpgrade (which uses wsHandshake, wsClients, and
store.setConnectedClients) validate a configured secret token (e.g., from env)
or verify the Origin header before proceeding—reject and destroy the socket or
respond 401 if the token/origin is missing/invalid so both HTTP and websocket
upgrade paths are gated by the same auth check.

---

Nitpick comments:
In `@observer/server.js`:
- Line 43: Replace the relative require for the event store with the repo's
absolute-import convention: locate the import that uses createEventStore (const
{ createEventStore } = require('./event-store')) and change it to the project's
absolute module path for event-store so it uses the same import pattern as other
files (keeping the createEventStore symbol unchanged).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b388ca8b-45c4-4615-abb6-3ba43a656185

📥 Commits

Reviewing files that changed from the base of the PR and between d37e93b and f088872.

📒 Files selected for processing (3)
  • eslint.config.js
  • observer/dashboard.html
  • observer/server.js

Comment thread eslint.config.js
Comment on lines +3 to +4
const observerOverride = {
files: ['observer/**'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the eslint.config.js file
find . -name "eslint.config.js" -type f

Repository: SynkraAI/aiox-core

Length of output: 80


🏁 Script executed:

# List files in the observer directory to see what exists
find . -type d -name "observer" 2>/dev/null

Repository: SynkraAI/aiox-core

Length of output: 72


🏁 Script executed:

# Search for observer directory and its contents
fd -t f "^.*" observer/ 2>/dev/null || echo "Checking with git..."
git ls-files | grep "^observer/" | head -20

Repository: SynkraAI/aiox-core

Length of output: 195


🏁 Script executed:

cat -n ./eslint.config.js

Repository: SynkraAI/aiox-core

Length of output: 7205


🏁 Script executed:

# Check ESLint documentation on flat config glob patterns
web_search
ESLint flat config files glob pattern matching behavior

Repository: SynkraAI/aiox-core

Length of output: 157


🏁 Script executed:

# Verify the actual observer directory structure one more time
find observer -type f -print

Repository: SynkraAI/aiox-core

Length of output: 128


🌐 Web query:

ESLint flat config files glob pattern matching observer/** behavior

💡 Result:

In ESLint flat config (eslint.config.js), glob patterns in the files and ignores properties of configuration objects use minimatch for matching. The pattern "observer/" matches the observer directory and all files and subdirectories within it recursively. A configuration object applies to a file if: - The file matches at least one pattern in the object's files array (or all files if no files key is present). - The file does not match any pattern in the object's ignores array. Global ignores are defined by a standalone object containing only an ignores array (e.g., { ignores: ["observer/"] }), which completely excludes matching files from linting, regardless of other configs. Local ignores only exclude from that specific config object. Paths in globs are relative to the directory containing eslint.config.js. Patterns use standard minimatch syntax: observer/ matches observer/any/sub/path/file.js but not observer.js or files outside observer/. No specific issues or special behavior were found for "observer/" in official docs or discussions; it behaves as a standard recursive directory glob. For directories, use observer/ (not observer/) in local ignores to exclude contents. Examples: - Apply rules to observer/: { files: ["observer/"], rules: { ... } } - Ignore observer/ globally: [{ ignores: ["observer/"] }, ...other configs] - Ignore observer/ locally: { files: ["/*"], ignores: ["observer/"], rules: { ... } }

Citations:


Narrow the observer override pattern to JS files only.

The pattern files: ['observer/**'] matches all files under the observer directory, including observer/dashboard.html. Since the override is designed for the CommonJS Node.js runtime (not static assets), restrict it to JavaScript files to explicitly declare the configuration's scope.

🩹 Minimal fix
-  files: ['observer/**'],
+  files: ['observer/*.js', 'observer/**/*.js'],
📝 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.

Suggested change
const observerOverride = {
files: ['observer/**'],
const observerOverride = {
files: ['observer/*.js', 'observer/**/*.js'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.js` around lines 3 - 4, The observer override currently uses
files: ['observer/**'] which matches all assets; change the glob to target only
JS files by updating the observerOverride.files pattern (the observerOverride
constant) to something like
['observer/**/*.js','observer/**/*.cjs','observer/**/*.mjs'] or at minimum
['observer/**/*.js'] so the CommonJS Node.js-specific override applies only to
JavaScript source files and not static assets like observer/dashboard.html.

Comment thread observer/dashboard.html
Comment on lines +618 to +620
setText('log-count', `(${logRows.length})`);
setText('m-total', liveState.metrics.total || logRows.length);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Render the footer metrics after applying the live event delta.

appendEventToLog() reads liveState.metrics.total before applyEventDelta() increments it, so the total badge stays one event behind during live updates. events/min also never changes after init. Update the metric state first, then render both footer values from liveState.metrics.

Also applies to: 646-647, 738-742

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/dashboard.html` around lines 618 - 620, The footer metrics are
rendered before the live event delta is applied, causing the total and
events/min to lag; in appendEventToLog() call applyEventDelta() first to update
liveState.metrics, then use liveState.metrics.total and
liveState.metrics.eventsPerMinute (or the appropriate metrics field) when
calling setText('m-total', ...) and setText('log-count', ...); make the same
change at the other occurrences (around the setText calls at the other noted
locations) so both total and events/min are computed from liveState.metrics
after the delta is applied.

Comment thread observer/dashboard.html
Comment on lines +672 to +676
if (liveState.bobStatus && data.agent && data.pid) {
liveState.bobStatus.active_terminals = liveState.bobStatus.active_terminals || [];
liveState.bobStatus.active_terminals.push({ agent: data.agent, pid: data.pid, task: data.task });
renderTerminals(liveState.bobStatus.active_terminals);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove finished agents from active_terminals too.

BobAgentSpawned appends terminal rows, but BobAgentCompleted and AgentDeactivated only touch activeAgents. If bob-status updates lag—or chokidar is unavailable—the "Active Terminals" card keeps showing completed agents indefinitely. Prune the matching terminal entries in those teardown paths and re-render.

Also applies to: 680-698

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/dashboard.html` around lines 672 - 676, The "Active Terminals" list
is only appended in the BobAgentSpawned path
(liveState.bobStatus.active_terminals and renderTerminals) but not pruned when
agents finish; update the BobAgentCompleted and AgentDeactivated handlers to
remove any entries from liveState.bobStatus.active_terminals that match the
completed/deactivated agent (match by agent and pid), then call
renderTerminals(liveState.bobStatus.active_terminals) to re-render; ensure you
guard for liveState.bobStatus and initialize active_terminals as an array if
needed before manipulating it.

Comment thread observer/dashboard.html
Comment on lines +726 to +734
case 'init': {
const { state, recentEvents } = msg.data || {};
if (state) {
liveState = Object.assign(liveState, state);
applyState(state);
}
if (recentEvents && Array.isArray(recentEvents)) {
recentEvents.forEach(appendEventToLog);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear or de-duplicate the log on each init.

Reconnects append the last 50 recentEvents onto the existing logRows buffer, so a transient socket drop duplicates the log and inflates the visible row count. Reset the current rows on init or skip events whose id is already present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/dashboard.html` around lines 726 - 734, The init handler currently
appends recentEvents onto the existing buffer, causing duplicates after
reconnect; in the case 'init' block (around liveState, applyState, recentEvents,
appendEventToLog) either clear the current UI log buffer before iterating (reset
logRows / DOM rows) or filter recentEvents to skip any event whose id already
exists in the log (compare event.id against existing log row ids) before calling
appendEventToLog; implement one of these fixes so reconnects replace or dedupe
the last-50 events instead of duplicating them.

Comment thread observer/server.js
Comment on lines +450 to +480
function startStatusWatcher(statusPath) {
if (!chokidar) {
console.log('[observer] chokidar not available — file watcher disabled');
return;
}

// Watch with ignoreInitial=false to get the current value on start
const watcher = chokidar.watch(statusPath, {
persistent: true,
ignoreInitial: false,
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
});

const onChanged = (filePath) => {
try {
const raw = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);
store.setBobStatus(data);
broadcast('status_update', data);
} catch (_e) {
// File temporarily unreadable (mid-write) — ignore
}
};

watcher.on('add', onChanged);
watcher.on('change', onChanged);

watcher.on('error', (_e) => {
// Watcher error — non-fatal, server continues
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Close the chokidar watcher when startServer() shuts down.

startStatusWatcher() creates a persistent watcher, but its handle is discarded. If a caller imports startServer() and later calls server.close(), the file watcher keeps running and can hold the process open. Return the watcher and close it from the server's 'close' path.

♻️ Minimal fix
 function startStatusWatcher(statusPath) {
   if (!chokidar) {
     console.log('[observer] chokidar not available — file watcher disabled');
-    return;
+    return null;
   }
@@
   watcher.on('error', (_e) => {
     // Watcher error — non-fatal, server continues
   });
+
+  return watcher;
 }
@@
 function startServer(port) {
   const server = http.createServer(handleRequest);
+  const watcher = startStatusWatcher(BOB_STATUS_PATH);
+
+  server.on('close', () => {
+    if (watcher) {
+      watcher.close().catch(() => {});
+    }
+  });
@@
-  startStatusWatcher(BOB_STATUS_PATH);
-
   return server;
 }

Also applies to: 520-522

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` around lines 450 - 480, startStatusWatcher currently
creates a persistent chokidar watcher and discards the handle, causing the
watcher to keep the process alive after shutdown; modify startStatusWatcher to
return the watcher instance (the variable watcher) and update the server
shutdown logic in startServer (or the server 'close' handler) to call
watcher.close() (or watcher.unwatch()/close equivalent) when server.close()
runs; also apply the same change to the similar watcher creation at the other
location referenced (around the 520-522 block) so all persistent watchers are
returned and explicitly closed on server shutdown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: agents Agent system related area: cli CLI tools (bin/, packages/aios-pro-cli/) area: core Core framework (.aios-core/core/) area: devops CI/CD, GitHub Actions (.github/) area: docs Documentation (docs/) area: health-check Health check system area: installer Installer and setup (packages/installer/) area: pro Pro features (pro/) area: synapse SYNAPSE context engine area: workflows Workflow system related mcp squad type: test Test coverage and quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants