Fix#10
Conversation
…irectory-issue fix: WebSocket error message, auto-reconnect on gate disconnect, and Gateway controls page
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughThis PR adds a complete gateway monitoring feature with resilient connectivity. The backend now reports server status (uptime, port, client count, metadata) via a new ChangesGateway Status Monitoring with Connection Resilience
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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.
Pull request overview
This PR improves the web dashboard’s resilience and observability by adding automatic WebSocket reconnection behavior, exposing a new gate.status protocol method on the server, and adding a new “Gateway” status page in the UI (plus terminal aliases) to display gate/server health.
Changes:
- Add client-side WebSocket reconnection/backoff and connection status subscriptions.
- Add a new Gateway page and route (sidebar + terminal aliases) to show gate uptime/clients and provider health.
- Add
gate.statushandling in the gate protocol and wire in server port + client count.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| web/tsconfig.tsbuildinfo | Updates TypeScript build info artifact (should not be committed). |
| web/src/ws.ts | Adds reconnect scheduling/backoff and a user-friendlier connection error message; exposes onStatus/getStatus. |
| web/src/pages/Terminal.tsx | Adds terminal aliases/help text for gate.status (“gateway”, “gate.info”). |
| web/src/pages/Overview.tsx | Refactors fetching into a callback and re-fetches on reconnect via onStatus. |
| web/src/pages/Gateway.tsx | New UI page for gate status + provider health + ping controls. |
| web/src/App.tsx | Adds sidebar link and route for the new Gateway page. |
| vercel.json | Defines Vercel build/output configuration for the dashboard (web/dist). |
| src/gate/server.ts | Passes server port + live client count into the WS protocol context. |
| src/gate/protocol.ts | Extends protocol context and implements gate.status response payload. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1 +1 @@ | |||
| {"root":["./src/App.tsx","./src/main.tsx","./src/ws.ts","./src/pages/Activity.tsx","./src/pages/Chat.tsx","./src/pages/Datasets.tsx","./src/pages/EmbedSearch.tsx","./src/pages/Evals.tsx","./src/pages/FineTune.tsx","./src/pages/Mcp.tsx","./src/pages/Metrics.tsx","./src/pages/Overview.tsx","./src/pages/Prompts.tsx","./src/pages/Providers.tsx","./src/pages/Sandbox.tsx","./src/pages/Search.tsx","./src/pages/SessionDetail.tsx","./src/pages/Sessions.tsx","./src/pages/Templates.tsx","./src/pages/Terminal.tsx","./src/pages/WhatsApp.tsx"],"errors":true,"version":"6.0.2"} No newline at end of file | |||
| {"root":["./src/App.tsx","./src/main.tsx","./src/ws.ts","./src/pages/Activity.tsx","./src/pages/Chat.tsx","./src/pages/Datasets.tsx","./src/pages/EmbedSearch.tsx","./src/pages/Evals.tsx","./src/pages/FineTune.tsx","./src/pages/Mcp.tsx","./src/pages/Metrics.tsx","./src/pages/Overview.tsx","./src/pages/Prompts.tsx","./src/pages/Providers.tsx","./src/pages/Sandbox.tsx","./src/pages/Search.tsx","./src/pages/SessionDetail.tsx","./src/pages/Sessions.tsx","./src/pages/Templates.tsx","./src/pages/Terminal.tsx","./src/pages/WhatsApp.tsx"],"version":"5.9.3"} No newline at end of file | |||
| useEffect(() => { | ||
| fetchStatus(); | ||
| const interval = setInterval(fetchStatus, 10_000); | ||
| const unsub = onStatus((s) => { | ||
| setConnected(s); | ||
| if (s) fetchStatus(); | ||
| }); | ||
| return () => { | ||
| clearInterval(interval); | ||
| unsub(); | ||
| }; | ||
| }, [fetchStatus]); |
| } catch (e: unknown) { | ||
| setError(String(e)); | ||
| } finally { |
| /> | ||
| <StatCard label="PID" value={String(status.pid)} sub={status.platform} /> | ||
| <StatCard label="Node" value={status.nodeVersion} sub="runtime" accent="var(--text2)" /> | ||
| <StatCard label="Started" value={fmtTs(status.startedAt).split(",")[1]?.trim() ?? ""} sub={fmtTs(status.startedAt).split(",")[0]} accent="var(--text2)" /> |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/pages/Overview.tsx (1)
124-170:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid the duplicate RPC burst on the first successful socket connect.
Line 173 starts the initial load, and Line 177 starts it again when that same initial
connect()flips toconnected. On a cold load, that duplicates every RPC infetchData()(sessions.stats,sessions.list,providers.ping, etc.), which adds avoidable backend load and creates response races between the two fetch rounds.Also applies to: 172-178
🤖 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 `@web/src/pages/Overview.tsx` around lines 124 - 170, The fetchData RPCs are being invoked twice on cold load because initial load and the socket 'connect' handler both call fetchData; add a one-time guard so fetchData only runs once per page mount. Add a boolean ref/state (e.g., hasFetchedRef) initialized false, set it true the first time fetchData runs, and update the socket 'connect' handler (the function that calls fetchData when connection becomes "connected") to call fetchData only if hasFetchedRef is false; leaving fetchData implementation unchanged but ensuring any callers set the guard to avoid duplicate RPC bursts.
🤖 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 `@vercel.json`:
- Around line 1-6: The current vercel.json only builds the static dashboard,
leaving the Express+WebSocket gate (src/gate/server.ts, which creates a
WebSocketServer and exposes JSON-RPC methods like gate.status) un-deployed; fix
by deploying the backend and wiring the frontend to it: either (A) deploy
src/gate/server.ts as a long-running Node service on a separate host and change
the frontend to use a configured WS URL (do not derive from
window.location.host), or (B) move to a full-stack host that runs the gate
process alongside the dashboard, or (C) refactor the gate into a
Vercel-compatible serverless/websocket solution—ensure the frontend reads the
backend host from a runtime config/env var (instead of window.location.host) so
the WebSocket connects to the running gate and gate.status works.
In `@web/src/pages/Gateway.tsx`:
- Line 162: The StatCard is using fragile comma-splitting and calls
fmtTs(status.startedAt) twice; instead call fmtTs(status.startedAt) once (or use
a Date/Intl.DateTimeFormat on status.startedAt) to produce separate locale-safe
date and time strings, then pass the time string as value and the date string as
sub to StatCard (refer to fmtTs and the StatCard usage for "Started" and
status.startedAt), avoiding redundant formatting and incorrect comma-split
logic.
In `@web/src/ws.ts`:
- Around line 30-39: scheduleReconnect currently leaves an armed timeout
(reconnectTimer) running even if connect() succeeds, so future outages inherit
stale backoff; modify the successful-connection path (the resolution of
connect() / the socket onopen handler) to cancel any pending retry by calling
clearTimeout(reconnectTimer) if reconnectTimer !== null, set reconnectTimer =
null, and reset reconnectAttempt to 0 (or the intended baseline) so subsequent
calls to scheduleReconnect can create a fresh timer and backoff. Ensure you
reference the existing symbols scheduleReconnect, reconnectTimer,
reconnectAttempt and connect() when making the change.
---
Outside diff comments:
In `@web/src/pages/Overview.tsx`:
- Around line 124-170: The fetchData RPCs are being invoked twice on cold load
because initial load and the socket 'connect' handler both call fetchData; add a
one-time guard so fetchData only runs once per page mount. Add a boolean
ref/state (e.g., hasFetchedRef) initialized false, set it true the first time
fetchData runs, and update the socket 'connect' handler (the function that calls
fetchData when connection becomes "connected") to call fetchData only if
hasFetchedRef is false; leaving fetchData implementation unchanged but ensuring
any callers set the guard to avoid duplicate RPC bursts.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: cf841068-65e6-4257-a09f-8012211d6ba8
📒 Files selected for processing (9)
src/gate/protocol.tssrc/gate/server.tsvercel.jsonweb/src/App.tsxweb/src/pages/Gateway.tsxweb/src/pages/Overview.tsxweb/src/pages/Terminal.tsxweb/src/ws.tsweb/tsconfig.tsbuildinfo
| { | ||
| "$schema": "https://openapi.vercel.sh/vercel.json", | ||
| "installCommand": "npm install --prefix web", | ||
| "buildCommand": "cd web && npm run build", | ||
| "outputDirectory": "web/dist" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify backend server exists and requires runtime execution
# Check for backend server entry point
echo "=== Backend server files ==="
fd -e ts -e js "server" src/gate/
# Check if WebSocket server is imported/used
echo -e "\n=== WebSocket server usage ==="
rg -n "WebSocket|createServer|listen" src/gate/server.ts
# Check package.json for backend dependencies
echo -e "\n=== Server runtime dependencies ==="
rg -A 5 "ws|express|fastify" package.jsonRepository: Jacobcdsmith/jclaw-framework
Length of output: 1004
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== vercel.json (root) ==="
ls -la vercel.json || true
cat -n vercel.json || true
echo -e "\n=== Any other vercel.json files ==="
fd -H -e json "vercel.json" . || true
echo -e "\n=== Root package.json ==="
ls -la package.json || true
cat -n package.json || true
echo -e "\n=== Backend startup script (if any) ==="
rg -n "\"start\"|\"dev\"|\"build\"|tsx|ts-node" package.json **/package.json || true
echo -e "\n=== Frontend websocket usage ==="
fd -e ts -e tsx "ws" web/src || true
rg -n "new WebSocket|WebSocket\\(|ws://|wss://" web/src || true
echo -e "\n=== Any Vercel/serverless functions configured ==="
rg -n "api/|functions/|serverless|vercel|`@vercel`|outputDirectory|routes" . || trueRepository: Jacobcdsmith/jclaw-framework
Length of output: 2231
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo -e "\n=== web/src/ws.ts (relevant sections) ==="
nl -ba web/src/ws.ts | sed -n '1,220p'
echo -e "\n=== Find WebSocket URL construction / env vars ==="
rg -n "getWsUrl|WS_URL|websocket|wss://|ws://|VITE_|process\\.env" web/src || true
echo -e "\n=== src/gate/server.ts (startup + routes) ==="
nl -ba src/gate/server.ts | sed -n '1,260p'
echo -e "\n=== Search for gateway status RPC usage (frontend) ==="
rg -n "gate\\.status|status\\b" web/src src || true
echo -e "\n=== deployment notes (if any) ==="
ls -la
fd -e md -a . && true
rg -n "vercel|deployment|ws url|getWsUrl|gate" README* **/*.md . || trueRepository: Jacobcdsmith/jclaw-framework
Length of output: 176
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo -e "\n=== web/src/ws.ts (relevant sections) ==="
wc -l web/src/ws.ts || true
cat -n web/src/ws.ts | sed -n '1,220p'
echo -e "\n=== Find WebSocket URL construction / env vars ==="
rg -n "getWsUrl|WS_URL|websocket|wss://|ws://|VITE_|process\\.env" web/src || true
echo -e "\n=== src/gate/server.ts (startup + routes) ==="
wc -l src/gate/server.ts || true
cat -n src/gate/server.ts | sed -n '1,260p'
echo -e "\n=== Search for gateway status RPC usage (frontend) ==="
rg -n "gate\\.status|\\bstatus\\b" web/src src/gate/protocol.ts src/gate/server.ts || true
echo -e "\n=== deployment notes (if any) ==="
fd -e md -a . || true
rg -n "vercel|deployment|ws url|getWsUrl|gate" README* **/*.md . || trueRepository: Jacobcdsmith/jclaw-framework
Length of output: 26546
Critical: Vercel deploys only the dashboard; the required gate (Express+WebSocket) server isn’t deployed.
vercel.json builds/deploys only web/dist, but the app’s backend is src/gate/server.ts (Express + WebSocketServer, plus JSON-RPC methods like gate.status). The frontend computes the WebSocket URL from window.location.host, so on Vercel it will try to connect WS to the Vercel-hosted origin—there will be no running gate server, causing WS/RPC features (including gate.status) to fail.
💡 Recommended deployment approaches
Option 1: Deploy backend separately
- Run
src/gate/server.tson a platform that supports long-running Node processes - Point the frontend WS URL to that backend host (instead of relying on same-origin)
Option 2: Deploy backend alongside the dashboard
- Use a full-stack hosting option that runs the Node gate server and serves the dashboard (instead of only static
web/dist)
Option 3: Convert to serverless
- Refactor the WS server to a Vercel-compatible serverless/websocket approach (only if architecture changes are acceptable)
🤖 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 `@vercel.json` around lines 1 - 6, The current vercel.json only builds the
static dashboard, leaving the Express+WebSocket gate (src/gate/server.ts, which
creates a WebSocketServer and exposes JSON-RPC methods like gate.status)
un-deployed; fix by deploying the backend and wiring the frontend to it: either
(A) deploy src/gate/server.ts as a long-running Node service on a separate host
and change the frontend to use a configured WS URL (do not derive from
window.location.host), or (B) move to a full-stack host that runs the gate
process alongside the dashboard, or (C) refactor the gate into a
Vercel-compatible serverless/websocket solution—ensure the frontend reads the
backend host from a runtime config/env var (instead of window.location.host) so
the WebSocket connects to the running gate and gate.status works.
| /> | ||
| <StatCard label="PID" value={String(status.pid)} sub={status.platform} /> | ||
| <StatCard label="Node" value={status.nodeVersion} sub="runtime" accent="var(--text2)" /> | ||
| <StatCard label="Started" value={fmtTs(status.startedAt).split(",")[1]?.trim() ?? ""} sub={fmtTs(status.startedAt).split(",")[0]} accent="var(--text2)" /> |
There was a problem hiding this comment.
Fix incorrect date/time splitting and avoid redundant formatting calls.
The comma-split logic produces incorrect output. For a locale-formatted timestamp like "Jun 8, 2026, 10:30:00 AM", splitting by comma yields ["Jun 8", " 2026", " 10:30:00 AM"], so [1] extracts only the year ("2026") as the main value and [0] extracts the partial date ("Jun 8") as the sub-text. Additionally, fmtTs(status.startedAt) is called twice, which is inefficient.
🔧 Proposed fix to correctly split date and time
- <StatCard label="Started" value={fmtTs(status.startedAt).split(",")[1]?.trim() ?? ""} sub={fmtTs(status.startedAt).split(",")[0]} accent="var(--text2)" />
+ <StatCard
+ label="Started"
+ value={new Date(status.startedAt).toLocaleTimeString(undefined, { timeStyle: "medium" })}
+ sub={new Date(status.startedAt).toLocaleDateString(undefined, { dateStyle: "medium" })}
+ accent="var(--text2)"
+ />This approach formats date and time separately, avoiding fragile string splitting and redundant calls.
🤖 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 `@web/src/pages/Gateway.tsx` at line 162, The StatCard is using fragile
comma-splitting and calls fmtTs(status.startedAt) twice; instead call
fmtTs(status.startedAt) once (or use a Date/Intl.DateTimeFormat on
status.startedAt) to produce separate locale-safe date and time strings, then
pass the time string as value and the date string as sub to StatCard (refer to
fmtTs and the StatCard usage for "Started" and status.startedAt), avoiding
redundant formatting and incorrect comma-split logic.
| function scheduleReconnect() { | ||
| if (reconnectTimer !== null) return; | ||
| const delay = Math.min(1_000 * 2 ** reconnectAttempt, MAX_RECONNECT_DELAY_MS); | ||
| reconnectAttempt += 1; | ||
| reconnectTimer = setTimeout(() => { | ||
| reconnectTimer = null; | ||
| // Errors are expected while the server is unavailable; scheduleReconnect will be | ||
| // called again via onclose if the attempt fails. | ||
| connect().catch(() => { }); | ||
| }, delay); |
There was a problem hiding this comment.
Clear the old backoff timer after a successful reconnect.
connect() can succeed before the scheduled retry fires. In that case, Line 50 resets the attempt counter, but the previous timeout is still armed. If the socket drops again before that timeout runs, Line 31 suppresses scheduling a new retry and the next reconnect inherits a stale delay from the prior outage.
Suggested fix
socket.onopen = () => {
ws = socket;
+ if (reconnectTimer !== null) {
+ clearTimeout(reconnectTimer);
+ reconnectTimer = null;
+ }
reconnectAttempt = 0;
notifyStatus(true);
resolve(socket);
};Also applies to: 48-50
🤖 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 `@web/src/ws.ts` around lines 30 - 39, scheduleReconnect currently leaves an
armed timeout (reconnectTimer) running even if connect() succeeds, so future
outages inherit stale backoff; modify the successful-connection path (the
resolution of connect() / the socket onopen handler) to cancel any pending retry
by calling clearTimeout(reconnectTimer) if reconnectTimer !== null, set
reconnectTimer = null, and reset reconnectAttempt to 0 (or the intended
baseline) so subsequent calls to scheduleReconnect can create a fresh timer and
backoff. Ensure you reference the existing symbols scheduleReconnect,
reconnectTimer, reconnectAttempt and connect() when making the change.
Summary by CodeRabbit
New Features
Chores