Skip to content

Add AIOX Visual Observer dashboard and WebSocket server#599

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

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

Conversation

@devoliverluccas
Copy link

@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

    • Added a Visual Observer dashboard for real-time monitoring with live WebSocket updates: agent/status, pipeline progress, active terminals, metrics, and filtered event logs.
    • Added an in-memory observer event store and a lightweight observer server to collect, persist, and broadcast events to the dashboard.
  • Chores

    • Marked the Observer as a standalone runtime in lint/config.
    • Added runtime data patterns to version-control exclusions.
    • 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

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

coderabbitai bot commented Mar 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de5dd660-c6f6-4020-b3e2-f1b4d15e8ea6

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • .aiox-core/data/entity-registry.yaml
  • .aiox-core/install-manifest.yaml
  • observer/event-store.js
✅ Files skipped from review due to trivial changes (1)
  • .aiox-core/install-manifest.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • observer/event-store.js

Walkthrough

Adds a new Observer subsystem: a Node.js HTTP+WebSocket server and in-memory event store, plus a standalone dashboard HTML for real-time visualization. Also updates lint and gitignore config to exclude observer runtime and Synapse runtime data patterns.

Changes

Cohort / File(s) Summary
Configuration
.synapse/.gitignore, eslint.config.js
Adds ignore patterns for Synapse runtime data (sessions/, cache/) and excludes the observer/** directory from ESLint project checks.
Observer — Dashboard
observer/dashboard.html
New self-contained HTML dashboard with client-side JS that connects via WebSocket, renders liveState (agents, pipeline, terminals, event log), supports filtering, auto-scroll, and reconnection logic.
Observer — Event Store
observer/event-store.js
New in-memory circular event store (max ~200 events) exposing createEventStore and methods: addEvent, setBobStatus, getRecentEvents, getState, setConnectedClients, reset; maintains derived serializable dashboard state and rate metrics.
Observer — Server
observer/server.js
New Node HTTP + WebSocket server: endpoints for POST /events, GET /status, GET /events/recent, serves dashboard, implements WebSocket handshake/broadcasts, integrates event-store, optional bob-status.json file watcher (chokidar), and exports startServer, broadcast, store.

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 (event payload)
    Server->>Store: addEvent(event)
    Note over Store: update circular buffer<br/>update derived dashboard state
    Server->>Server: broadcast(event + state) to WS clients
    Server->>Client: send via WebSocket
    Client->>Client: update liveState and UI

    Client->>Server: WebSocket connect
    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 changes in the PR, which adds a new observer tool with a 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
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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
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

@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 on lines +64 to +65
// Observer server — standalone runtime tool, not part of TS project
'observer/**',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep observer/** under lint coverage.

The existing JS config already supports CommonJS/ES2022, so this blanket ignore turns off the only automated checks on the new runtime surface added by this PR. It would also hide real regressions here—observer/event-store.js already contains a use-before-declaration bug that this ignore would suppress. If a few rules need relaxing, add a targeted override instead of excluding the directory.

🧹 Minimal fix
-      // Observer server — standalone runtime tool, not part of TS project
-      'observer/**',
📝 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
// Observer server — standalone runtime tool, not part of TS project
'observer/**',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.js` around lines 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.

Comment on lines +480 to +486
terminals.forEach((t) => {
const row = document.createElement('div');
row.className = 'terminal-row';
row.innerHTML =
`<span class="terminal-agent">${t.agent || '?'}</span>` +
`<span class="terminal-pid">pid:${t.pid || '?'}</span>` +
`<span class="terminal-task">${t.task || ''}</span>`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Stop feeding untrusted payloads into innerHTML.

observer/server.js Lines 381-383 and 439-442 forward arbitrary event/status data straight to the browser. Interpolating t.agent, t.task, event.type, and summary into HTML strings makes the dashboard script-executable from a crafted payload. Build these rows with createElement/textContent instead.

Also applies to: 575-586

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

In `@observer/dashboard.html` around lines 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.

Comment on lines +518 to +540
function applyState(state) {
if (!state) return;

if (state.sessionId) setText('session-id', state.sessionId.slice(0, 12) + '…');
if (state.uptime !== undefined) serverUptime = state.uptime;
if (state.connectedClients !== undefined) {
setText('client-count', state.connectedClients);
setText('m-clients', state.connectedClients);
}

updateAgentCard(state);
renderPipeline(state.pipeline);

// Bob status terminals
if (state.bobStatus && state.bobStatus.active_terminals) {
renderTerminals(state.bobStatus.active_terminals);
}

// Metrics
if (state.metrics) {
setText('m-total', state.metrics.total || 0);
setText('m-rate', state.metrics.eventsPerMin || 0);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the metrics bar sourced from liveState.metrics.

The init payload carries both state.metrics and a separate recentEvents buffer (observer/server.js Lines 245-250). applyState() renders the real totals, but appendEventToLog() immediately overwrites m-total with logRows.length, and later event deltas only mutate liveState.metrics.total. After init, the metrics bar drifts from the store state and "total events" becomes retained-row count instead of the cumulative metric.

Also applies to: 563-598, 617-624

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

In `@observer/dashboard.html` around lines 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.

Comment on lines +723 to +735
case 'status_update': {
const data = msg.data;
liveState.bobStatus = data;
if (data) {
if (data.pipeline) {
liveState.pipeline.current = data.pipeline.current_stage;
liveState.pipeline.completed = data.pipeline.completed_stages || [];
renderPipeline(liveState.pipeline);
setText('agent-phase', liveState.pipeline.current || '—');
}
if (data.current_agent && data.current_agent.id) {
liveState.currentAgent = data.current_agent.id;
updateAgentCard(liveState);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Update liveState.currentPhase on status_update before re-rendering the agent card.

When the same status payload contains both pipeline and current_agent—the normal shape from observer/server.js Lines 439-442—updateAgentCard(liveState) rewrites the phase label from liveState.currentPhase. Since this branch never updates that field, the card can jump back to stale phase data or .

🔧 Suggested fix
         if (data.pipeline) {
+          liveState.currentPhase = data.pipeline.current_stage || null;
           liveState.pipeline.current = data.pipeline.current_stage;
           liveState.pipeline.completed = data.pipeline.completed_stages || [];
           renderPipeline(liveState.pipeline);
           setText('agent-phase', liveState.pipeline.current || '—');
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/dashboard.html` around lines 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.

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

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 on lines +308 to +320
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
} catch (e) {
reject(e);
}
});
req.on('error', reject);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cap request bodies in readBody().

readBody() buffers the full payload with no limit, so a single oversized or slow client can force unbounded memory growth and take the observer down. Reject over a small maximum and map that path to 413 Payload Too Large.

🛡️ Suggested fix
+const MAX_BODY_BYTES = 1024 * 1024;
+
 function readBody(req) {
   return new Promise((resolve, reject) => {
     const chunks = [];
-    req.on('data', (chunk) => chunks.push(chunk));
+    let total = 0;
+    req.on('data', (chunk) => {
+      total += chunk.length;
+      if (total > MAX_BODY_BYTES) {
+        reject(Object.assign(new Error('Payload too large'), { statusCode: 413 }));
+        req.destroy();
+        return;
+      }
+      chunks.push(chunk);
+    });
     req.on('end', () => {
       try {
         resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
@@
-  } catch (_e) {
+  } catch (e) {
     try {
-      res.writeHead(400, { 'Content-Type': 'text/plain' });
-      res.end('Bad Request');
+      const status = e && e.statusCode ? e.statusCode : 400;
+      res.writeHead(status, { 'Content-Type': 'text/plain' });
+      res.end(status === 413 ? 'Payload Too Large' : 'Bad Request');
     } catch (__e) {
       // Response already sent
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observer/server.js` around lines 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.

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
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.

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.

3 participants