feat(dashboard): Add authenticated Junior dashboard#448
Conversation
Add a packaged Junior dashboard with Better Auth, reporting APIs, conversation views, transcript redaction, and Sentry links. Replace the old diagnostics dashboard routes with the dashboard package and wire the example app to run it locally. Capture safer conversation metadata for dashboard and trace reporting while redacting private conversation inputs and outputs. Add dashboard docs, policy coverage, and tests for the route and reporting behavior. Co-Authored-By: GPT-5 Codex <codex@openai.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Replace dashboard path and auth URL slash-trimming regexes with linear helpers so CodeQL does not flag route inputs as polynomial regular expression risks. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Cache standalone dashboard handler initialization through a single promise so concurrent first requests do not create duplicate dashboard app instances. Co-Authored-By: GPT-5 Codex <codex@openai.com>
- Extend auth middleware to cover page sub-routes when basePath is "/";
app.use("/", ...) only matches the exact root in Hono, leaving
/conversations, /sessions, and their children unprotected
- Make the initial recordAgentTurnSessionSummary(state='running') call
fire-and-forget so it does not delay reply generation; the 'running'
state record is best-effort tracking and must not block the turn
- Add GEN_AI_SERVER_ADDRESS to @/chat/pi/client mocks in four unit test
files that mock the module without this export; respond.ts uses the
constant and vitest strict mocks reject missing exports
Action taken on behalf of David Cramer.
---
[View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780119001.371349%22)
Co-authored-by: David Cramer <david@sentry.io>
| return conversations.filter( | ||
| (conversation) => !isActiveConversation(conversation), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Recent filter hides active conversations
Medium Severity
The default recent conversations filter drops any row with an active turn, so the Flight Recorder’s initial view omits in-progress work unless users switch to active or all.
Reviewed by Cursor Bugbot for commit c20ae4d. Configure here.
Missed GEN_AI_SERVER_PORT=443 in the previous mock patch. The export was added alongside GEN_AI_SERVER_ADDRESS in this PR; respond.ts uses both at line 1165 and vitest strict mocks reject any missing export. Action taken on behalf of David Cramer. --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780119001.371349%22) Co-authored-by: David Cramer <david@sentry.io>
| ): Promise<DashboardConversationReport> { | ||
| const summaries = (await listAgentTurnSessionSummaries(200)).filter( | ||
| (summary) => summary.conversationId === conversationId, | ||
| ); |
There was a problem hiding this comment.
Conversation detail truncates globally
Medium Severity
getConversation loads the 200 most recently updated turn summaries across the whole runtime, then filters by conversation id. Busy deployments can omit older turns (or an entire permalink) even when checkpoint data still exists for that conversation.
Reviewed by Cursor Bugbot for commit 802a18d. Configure here.
When resolveConversationPrivacy returns undefined (no recognizable Slack channel identifier), privacy checks using === "private" incorrectly take the expose-content path. traced-stream.ts already uses the correct pattern (=== "public" to expose, else redact). Align respond.ts input and output message serialization and reporting.ts session title/channel redaction to use !== "public", so undefined falls to the safe metadata-only path. Fixes Warden finding EZN-84H. Action taken on behalf of David Cramer. --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780119001.371349%22) Co-authored-by: David Cramer <david@sentry.io>
|
|
||
| function forbidden(): Response { | ||
| return Response.json({ error: "forbidden" }, { status: 403 }); | ||
| } |
There was a problem hiding this comment.
Forbidden responses break browser pages
Medium Severity
After Google sign-in, users who have a valid session but fail domain or email policy always receive a JSON 403 body. Browser navigations to / or conversation pages never get an HTML error or redirect, unlike unauthenticated requests handled by unauthorized.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 16f14b2. Configure here.
- Change advisor/tool.ts privacy guards from === "private" to !== "public"
to match the fail-closed pattern mandated by specs/data-redaction-policy.md
and used in respond.ts, reporting.ts, and traced-stream.ts; the existing
?? "private" default made it functionally safe but the pattern was
inconsistent and fragile
- Add dashboard-routes test covering unauthenticated access to /conversations,
/conversations/:id, /sessions, and /sessions/:id at root basePath; the
previous test only verified / and would not have caught the Hono routing
gap where app.use("/", ...) skips sub-routes
Action taken on behalf of David Cramer.
---
[View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780119001.371349%22)
Co-authored-by: David Cramer <david@sentry.io>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1d31eac. Configure here.
| credentials: "same-origin", | ||
| method: "POST", | ||
| }); | ||
| window.location.assign("/"); |
There was a problem hiding this comment.
Sign-out ignores dashboard base path
Medium Severity
After sign-out, the client always navigates to / and posts to /api/auth/sign-out. Deployments that mount the dashboard under a non-root basePath (supported by juniorDashboardNitro) leave the SPA on the wrong URL and may call the wrong auth path if authPath is customized.
Reviewed by Cursor Bugbot for commit 1d31eac. Configure here.


Add a packaged Junior dashboard that runs as a Nitro plugin with Better Auth-protected routes, a React command center, conversation lists, and permalinked transcript views. This replaces the old diagnostics dashboard surface with a deployable dashboard package and wires the example app for local development.
Dashboard Package
The new @sentry/junior-dashboard package exposes Nitro and handler entry points, dashboard APIs, route tests, docs, and a dark-mode UI for command center stats, turn duration history, and conversation details. Dashboard points and conversation rows link to stable conversation permalinks, and Sentry links appear when the runtime has enough Sentry configuration.
Conversation Privacy
Junior now classifies conversation privacy and redacts private message, thinking, tool argument, and tool result content before exposing dashboard APIs or trace metadata. Private views keep safe metadata such as actor, status, message counts, tool names, timing, and byte sizes without leaking raw transcript content.
Instrumentation And Packaging
Turn tracing captures more OpenTelemetry-aligned conversation metadata while respecting redaction policy. @sentry/node and @sentry/starlight-theme concrete versions are pinned through pnpm catalogs, and Junior owns its Sentry SDK dependency directly instead of requiring apps to install it separately.