Skip to content

feat(dashboard): Add authenticated Junior dashboard#448

Open
dcramer wants to merge 7 commits into
mainfrom
feat/junior-dashboard
Open

feat(dashboard): Add authenticated Junior dashboard#448
dcramer wants to merge 7 commits into
mainfrom
feat/junior-dashboard

Conversation

@dcramer
Copy link
Copy Markdown
Member

@dcramer dcramer commented May 30, 2026

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.

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>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
junior-docs Ready Ready Preview, Comment May 30, 2026 6:56am

Request Review

Comment thread packages/junior-dashboard/src/app.ts Fixed
Comment thread packages/junior-dashboard/src/nitro.ts Fixed
Comment thread packages/junior-dashboard/src/handler.ts
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>
Comment thread packages/junior-dashboard/src/app.ts
- 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),
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c20ae4d. Configure here.

Comment thread packages/junior/src/reporting.ts
Comment thread packages/junior/src/chat/respond.ts
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,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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 });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ 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("/");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1d31eac. Configure here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants