Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8e0fe04
feat(dashboard): Add authenticated Junior dashboard
dcramer May 30, 2026
a02797a
fix(dashboard): Avoid regex slash trimming
dcramer May 30, 2026
50a6cb3
fix(dashboard): Share handler initialization
dcramer May 30, 2026
7b489e4
fix(dashboard): fix auth coverage, test mocks, and reply parallelism
sentry-junior[bot] May 30, 2026
28a0e8a
fix(tests): add GEN_AI_SERVER_PORT to pi/client mocks
sentry-junior[bot] May 30, 2026
7d5296f
fix(privacy): treat undefined conversation privacy as non-public
sentry-junior[bot] May 30, 2026
378d566
fix(privacy): align advisor tool and add sub-route auth test
sentry-junior[bot] May 30, 2026
0123461
fix(dashboard): Align reporting with session records
dcramer May 31, 2026
1bae1aa
ref(dashboard): Apply component styling policy
dcramer May 31, 2026
b9f1fdc
fix(dashboard): Polish transcript surfaces
dcramer May 31, 2026
2b37668
ref(dashboard): Tighten conversation list framing
dcramer May 31, 2026
1487de3
ref(dashboard): Flatten conversation detail view
dcramer May 31, 2026
04b2810
ref(dashboard): Decompose dashboard components
dcramer May 31, 2026
f9cfb87
fix(dashboard): Address dashboard review feedback
dcramer May 31, 2026
c042e7b
fix(dashboard): Require verified allowlist emails
dcramer May 31, 2026
c0fe311
fix(dashboard): Preserve resumable turn projections
dcramer May 31, 2026
588625a
fix(dashboard): Preserve private redaction state
dcramer May 31, 2026
a79cdda
fix(runtime): Capture summary checkpoint failures
dcramer May 31, 2026
aa79188
fix(dashboard): Index turn summaries by conversation
dcramer May 31, 2026
d16817f
fix(dashboard): Restart sign-in after API auth expiry
dcramer May 31, 2026
e94f527
fix(dashboard): Retry handler initialization after failure
dcramer May 31, 2026
96db416
style(dashboard): Use Tailwind for forbidden fallback
dcramer May 31, 2026
e71b5bb
fix(dashboard): Harden private metadata handling
dcramer May 31, 2026
24cf7d4
docs(dashboard): Clarify route ownership
dcramer May 31, 2026
2d01fdd
fix(dashboard): Sanitize session identity responses
dcramer May 31, 2026
d07cbf4
fix(example): Gate dashboard auth bypass to local dev
dcramer May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ targets:
- name: npm
id: "@sentry/junior-agent-browser"
includeNames: /^sentry-junior-agent-browser-\d.*\.tgz$/
- name: npm
id: "@sentry/junior-dashboard"
includeNames: /^sentry-junior-dashboard-\d.*\.tgz$/
- name: npm
id: "@sentry/junior-datadog"
includeNames: /^sentry-junior-datadog-\d.*\.tgz$/
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
pnpm --filter @sentry/junior pack --pack-destination artifacts
pnpm --filter @sentry/junior-plugin-api pack --pack-destination artifacts
pnpm --filter @sentry/junior-agent-browser pack --pack-destination artifacts
pnpm --filter @sentry/junior-dashboard pack --pack-destination artifacts
pnpm --filter @sentry/junior-datadog pack --pack-destination artifacts
pnpm --filter @sentry/junior-github pack --pack-destination artifacts
pnpm --filter @sentry/junior-hex pack --pack-destination artifacts
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ coverage
dist/
/packages/docs/.astro
packages/junior/dist
.codex/environments/
# Auto-generated by dotagents — do not commit these files.
.agents/.gitignore
# Generated by eval replay auto mode; existing tracked recordings stay tracked.
packages/junior-evals/.vitest-evals/recordings/**/*.json
.env*
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Co-Authored-By: (agent model name) <email>
- `policies/README.md` (when to add a policy doc and how policy docs should stay scoped)
- `policies/code-comments.md` (repo default for code comments, docstrings, and exported-function JSDoc)
- `policies/evals.md` (evals as behavior integration tests and rubric authoring boundaries)
- `policies/frontend-components.md` (Tailwind colocation and component-owned frontend styling)
- `policies/interface-design.md` (naming, module paths, and minimal interface boundaries)
- `policies/policy-template.md` (template for adding new policy docs)

Expand Down Expand Up @@ -107,6 +108,7 @@ Co-Authored-By: (agent model name) <email>
- `TELEMETRY.spec.md` (format contract for repository-root telemetry maps)
- `specs/index.md` (spec taxonomy, naming rules, and canonical vs archive guidance)
- `specs/security-policy.md` (global runtime/container/token security policy)
- `specs/data-redaction-policy.md` (conversation privacy classification and raw payload redaction policy)
- `specs/chat-architecture.md` (chat composition, service, and test-seam architecture contract)
- `specs/agent-turn-handling.md` (agent user-message response policy: reply/silence, tool use, Slack side effects, resumed turns, and completion)
- `specs/slack-agent-delivery.md` (Slack entry surfaces, reply delivery, continuation, files, images, and resume behavior contract)
Expand All @@ -126,5 +128,6 @@ Co-Authored-By: (agent model name) <email>
- `specs/plugin.md` (plugin architecture for self-contained provider integrations)
- `specs/plugin-manifest.md` (plugin manifest fields and validation contract)
- `specs/plugin-runtime.md` (plugin discovery, loading, skills, and MCP runtime contract)
- `specs/dashboard.md` (authenticated dashboard, stateless Better Auth, and reporting boundary contract)
- `specs/testing.md` (testing taxonomy and layer boundaries: unit/integration/eval)
- Historical evaluations and superseded trackers live under `specs/archive/`.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This repo uses Craft for manual lockstep npm releases of:
- `@sentry/junior`
- `@sentry/junior-plugin-api`
- `@sentry/junior-agent-browser`
- `@sentry/junior-dashboard`
- `@sentry/junior-datadog`
- `@sentry/junior-github`
- `@sentry/junior-hex`
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Start here:
| `@sentry/junior` | Core Slack bot runtime |
| `@sentry/junior-plugin-api` | Lightweight trusted plugin API types and helpers |
| `@sentry/junior-agent-browser` | Agent Browser plugin package for browser automation |
| `@sentry/junior-dashboard` | Authenticated dashboard package for Junior runtime diagnostics |
| `@sentry/junior-datadog` | Datadog plugin package for observability workflows through Datadog's Pup CLI |
| `@sentry/junior-github` | GitHub plugin package for issue workflows |
| `@sentry/junior-hex` | Hex plugin package for data warehouse query workflows |
Expand Down
1 change: 1 addition & 0 deletions apps/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Copy `.env.example` and set:
- `JUNIOR_SECRET` (required outside `pnpm dev`; the local wrapper supplies a dev-only secret when unset)
- `JUNIOR_SCHEDULER_SECRET` or `CRON_SECRET` (optional for `pnpm dev`; the local wrapper supplies a dev-only heartbeat secret when both are unset)
- `NOTION_TOKEN` (optional, enables the bundled Notion plugin)
- Dashboard auth is enabled by default. `pnpm dev` disables dashboard auth only for local non-Vercel development.

## Wiring

Expand Down
19 changes: 19 additions & 0 deletions apps/example/nitro.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { defineConfig } from "nitro";
import { juniorDashboardNitro } from "@sentry/junior-dashboard/nitro";
import { juniorNitro } from "@sentry/junior/nitro";
import { examplePluginPackages } from "./plugin-packages";

function isVercelEnvironment(): boolean {
return Boolean(
process.env.VERCEL?.trim() ||
process.env.VERCEL_ENV?.trim() ||
process.env.VERCEL_URL?.trim() ||
process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim(),
);
}

/** Return whether the example dashboard should require browser auth. */
export function exampleDashboardAuthRequired(): boolean {
return process.env.NODE_ENV !== "development" || isVercelEnvironment();
}

export default defineConfig({
preset: "vercel",
modules: [
Expand All @@ -10,6 +25,10 @@ export default defineConfig({
packages: examplePluginPackages,
},
}),
juniorDashboardNitro({
authRequired: exampleDashboardAuthRequired(),
allowedGoogleDomains: ["sentry.io"],
Comment thread
sentry-warden[bot] marked this conversation as resolved.
}),
],
routes: {
"/**": { handler: "./server.ts" },
Expand Down
3 changes: 2 additions & 1 deletion apps/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"predev": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build",
"dev": "nitro dev",
"build": "junior snapshot create && nitro build",
"preview": "nitro preview",
Expand All @@ -11,13 +12,13 @@
"dependencies": {
"@sentry/junior": "workspace:*",
"@sentry/junior-agent-browser": "workspace:*",
"@sentry/junior-dashboard": "workspace:*",
"@sentry/junior-datadog": "workspace:*",
"@sentry/junior-github": "workspace:*",
"@sentry/junior-hex": "workspace:*",
"@sentry/junior-linear": "workspace:*",
"@sentry/junior-notion": "workspace:*",
"@sentry/junior-sentry": "workspace:*",
"@sentry/node": "10.53.1",
"hono": "^4.12.22"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
"lint": "pnpm --filter @sentry/junior lint",
"lint:fix": "pnpm --filter @sentry/junior lint:fix",
"lint-staged": "lint-staged",
"build": "pnpm --filter @sentry/junior build",
"build": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build",
"build:example": "pnpm --filter @sentry/junior-example build",
"docs:dev": "pnpm --filter @sentry/junior-docs dev",
"docs:build": "pnpm --filter @sentry/junior-docs build",
"docs:check": "pnpm --filter @sentry/junior-docs check",
"release:check": "node scripts/check-release-config.mjs",
"start": "pnpm --filter @sentry/junior-example dev",
"test": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior test",
"test": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior test && pnpm --filter @sentry/junior-dashboard test",
"test:watch": "pnpm --filter @sentry/junior test:watch",
"evals": "pnpm --filter @sentry/junior-evals evals",
"evals:record": "pnpm --filter @sentry/junior-evals evals:record",
"typecheck": "pnpm --filter @sentry/junior-plugin-api typecheck && pnpm --filter @sentry/junior-scheduler typecheck && pnpm --filter @sentry/junior typecheck && pnpm --filter @sentry/junior-testing typecheck && pnpm --filter @sentry/junior-example typecheck",
"typecheck": "pnpm --filter @sentry/junior-plugin-api typecheck && pnpm --filter @sentry/junior-scheduler typecheck && pnpm --filter @sentry/junior typecheck && pnpm --filter @sentry/junior-dashboard typecheck && pnpm --filter @sentry/junior-testing typecheck && pnpm --filter @sentry/junior-example typecheck",
"skills:check": "pnpm --filter @sentry/junior skills:check"
},
"simple-git-hooks": {
Expand Down
1 change: 1 addition & 0 deletions packages/docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default defineConfig({
label: "Security Hardening",
link: "/operate/security-hardening/",
},
{ label: "Dashboard", link: "/operate/dashboard/" },
{
label: "Sandbox Snapshots",
link: "/operate/sandbox-snapshots/",
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/check": "^0.9.9",
"@astrojs/starlight": "^0.39.2",
"@sentry/starlight-theme": "^0.7.0",
"@sentry/starlight-theme": "catalog:",
"astro": "^6.3.7",
"starlight-typedoc": "^0.23.0",
"typedoc": "^0.28.19",
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/content/docs/contribute/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ If your team account requires an explicit Vercel scope, add `--scope <team-slug>
pnpm dev
```

This starts the example app on `http://localhost:3000` by default.
This starts the example app on `http://localhost:3000` by default. It also rebuilds and watches the workspace packages that the example app consumes, so dashboard and runtime package edits are reflected without manually rebuilding first.

## Common checks

Expand Down
1 change: 1 addition & 0 deletions packages/docs/src/content/docs/contribute/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Junior uses lockstep package releases for:
- `@sentry/junior`
- `@sentry/junior-plugin-api`
- `@sentry/junior-agent-browser`
- `@sentry/junior-dashboard`
- `@sentry/junior-datadog`
- `@sentry/junior-github`
- `@sentry/junior-hex`
Expand Down
120 changes: 120 additions & 0 deletions packages/docs/src/content/docs/operate/dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Dashboard
description: Mount the authenticated Junior dashboard with Google domain auth.
type: tutorial
summary: Add the dashboard package to a Nitro deployment and protect diagnostics with Better Auth and Google domain authorization.
prerequisites:
- /start-here/existing-app/
- /reference/config-and-env/
related:
- /reference/handler-surface/
- /operate/security-hardening/
- /start-here/verify-and-troubleshoot/
---

Use `@sentry/junior-dashboard` when you want browser access to Junior runtime diagnostics without exposing plugin, skill, or filesystem discovery publicly. The dashboard mounts into the same Nitro deployment as Junior, but its Better Auth session only protects dashboard routes.

## Install

Install the dashboard package next to `@sentry/junior`:

```bash
pnpm add @sentry/junior-dashboard
```

## Mount the routes

Add `juniorDashboardNitro()` before the catch-all Junior route. Configure the Google Workspace domain that should be allowed to view the dashboard:

```ts title="nitro.config.ts"
import { defineConfig } from "nitro";
import { juniorDashboardNitro } from "@sentry/junior-dashboard/nitro";
import { juniorNitro } from "@sentry/junior/nitro";

export default defineConfig({
preset: "vercel",
modules: [
juniorNitro({
plugins: {
packages: ["@sentry/junior-sentry"],
},
}),
juniorDashboardNitro({
allowedGoogleDomains: ["sentry.io"],
trustedOrigins: ["https://<your-domain>"],
}),
],
routes: {
"/**": { handler: "./server.ts" },
},
});
```

You can also provide the same authorization policy through deployment environment variables when the handler is loaded outside Nitro's virtual module path:

| Variable | Purpose |
| ---------------------------------- | ------------------------------------------------------------- |
| `JUNIOR_DASHBOARD_GOOGLE_DOMAINS` | Comma-separated or JSON array of allowed Google domains. |
| `JUNIOR_DASHBOARD_ALLOWED_EMAILS` | Comma-separated or JSON array of explicit email allowlist. |
| `JUNIOR_DASHBOARD_TRUSTED_ORIGINS` | Comma-separated or JSON array of Better Auth trusted origins. |
| `JUNIOR_DASHBOARD_AUTH_REQUIRED` | Set to `false` only for explicit local dashboard auth bypass. |

The dashboard package owns these routes:

| Route | Purpose |
| ------------------ | --------------------------------------- |
| `/` | Authenticated command-center UI. |
| `/conversations` | Authenticated conversation-history UI. |
| `/api/dashboard/*` | Authenticated dashboard JSON APIs. |
| `/api/auth/*` | Better Auth Google login and callbacks. |

`/health` remains the public minimal Junior runtime health response.

The current dashboard API slices are:

| Endpoint | Purpose |
| -------------------------------------------- | -------------------------------------------------------------------------------------- |
| `/api/dashboard/health` | Health status for the command center pulse. |
| `/api/dashboard/runtime` | Runtime paths, providers, skills, and packages. |
| `/api/dashboard/plugins` | Loaded plugin list. |
| `/api/dashboard/skills` | Discovered skill list. |
| `/api/dashboard/sessions` | Recent conversation feed from turn-session checkpoints. |
| `/api/dashboard/conversations/:conversation` | Expiring conversation transcript; private conversations return redacted metadata only. |
| `/api/dashboard/config` | Safe dashboard config signals and feature readiness. |
| `/api/dashboard/me` | Signed-in dashboard identity. |

The dashboard UI is a React client using React Router for browser views and TanStack Query to poll dashboard APIs. `/` shows command-center health and recent turn durations; `/conversations` shows conversation history; `/conversations/:conversation` shows the transcript and turn/tool-call detail for one conversation. The dashboard does not wrap Slack webhooks, provider OAuth callbacks, sandbox egress, or `/api/internal/*`.
The conversation feed is a bounded metadata index with the same expiration policy as turn-session checkpoints. Conversation detail reads transcript data from the expiring checkpoint message store, so old transcripts disappear when checkpoint state expires. When `SENTRY_DSN` initializes the runtime and `SENTRY_ORG_SLUG` is set, conversation rows include a Sentry conversation link; when the runtime captures a trace ID, conversation detail shows it with the turn metadata.
Dashboard dates use `JUNIOR_TIMEZONE`, defaulting to `America/Los_Angeles`.

## Configure Google auth

Create a Google OAuth client for the deployment origin. Add this redirect URI:

```text
https://<your-domain>/api/auth/callback/google
```

Set the required environment variables:

| Variable | Purpose |
| ---------------------- | --------------------------- |
| `GOOGLE_CLIENT_ID` | Google OAuth client ID. |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret. |

Dashboard cookies are signed with `JUNIOR_SECRET` by default. Set `BETTER_AUTH_SECRET` only when you need a separate rotation boundary for browser sessions.
Dashboard callbacks use `JUNIOR_BASE_URL`, Vercel URL envs, or local dev by default. Set `BETTER_AUTH_URL` only when dashboard auth needs a different public origin.

## Verify

After deployment:

1. `GET https://<your-domain>/health` returns a minimal health JSON response.
2. `GET https://<your-domain>/api/info` returns `404`.
3. Opening `https://<your-domain>/` starts Google login.
4. A user from the configured Google Workspace domain reaches the dashboard.
5. A user outside the configured domain receives `403`.

## Next step

Use [Security Hardening](/operate/security-hardening/) to review production auth boundaries, then use [Verify & Troubleshoot](/start-here/verify-and-troubleshoot/) for deployment smoke checks.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ title: "createApp"

> **createApp**(`options?`): `Promise`\<`Hono`\<`BlankEnv`, `BlankSchema`, `"/"`\>\>

Defined in: [app.ts:179](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L179)
Defined in: [app.ts:177](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L177)

Create a Hono app with all Junior routes.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ prev: false
title: "JuniorAppOptions"
---

Defined in: [app.ts:32](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L32)
Defined in: [app.ts:30](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L30)

## Properties

### configDefaults?

> `optional` **configDefaults?**: `Record`\<`string`, `unknown`\>

Defined in: [app.ts:34](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L34)
Defined in: [app.ts:32](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L32)

Install-wide provider defaults (`provider.key` format). Channel overrides take precedence.

Expand All @@ -23,7 +23,7 @@ Install-wide provider defaults (`provider.key` format). Channel overrides take p

> `optional` **plugins?**: `PluginConfig` \| `JuniorPlugin`[]

Defined in: [app.ts:42](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L42)
Defined in: [app.ts:40](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L40)

Plugin packages/overrides, or trusted plugin instances loaded by this app.

Expand All @@ -37,4 +37,4 @@ their package config is merged with the catalog bundled by `juniorNitro()`.

> `optional` **waitUntil?**: `WaitUntilFn`

Defined in: [app.ts:43](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L43)
Defined in: [app.ts:41](https://github.com/getsentry/junior/blob/main/packages/junior/src/app.ts#L41)
Loading
Loading