Skip to content

feat(web): owner analytics dashboard at /u/<handle>/dashboard - #164

Merged
khaliqgant merged 3 commits into
mainfrom
feat/company-dashboard
Oct 8, 2026
Merged

khaliqgant merged 3 commits into
mainfrom
feat/company-dashboard

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

The company dashboard from relay-agent's docs/ANALYTICS_BRIEF.md §5. Backend: AgentWorkforce/relay-agent#16 (link, exchange, and stats endpoints). Router companions: #162, which keeps /u/* and ?grant= URLs out of the traffic recorder.

Flow

  1. The owner opens https://agentrelay.com/u/<handle>/dashboard?grant=<signed grant>, a 15-minute single-use link from POST /api/v1/agents/<handle>/manage/dashboard-link.

  2. A beforeFiles rewrite in next.config.mjs, keyed on a non-empty grant query, sends that request to the route handler /u/<handle>/dashboard/exchange. No page, layout, or PostHog code runs.

    • proxy.ts was not used: Next 16 proxies run on the Node runtime, which OpenNext Cloudflare 1.20 rejects.
    • The rewrite's value: '.+' is explicit because OpenNext treats a query condition with no value as always matching.
  3. The handler validates the handle (404 if invalid) and exchanges the grant server-to-server at arelay.to/api/v1/agents/<handle>/dashboard/exchange.

    • On success it sets __Secure-relay_dashboard with HttpOnly; Secure; SameSite=Lax; Path=/u/<handle>/dashboard; Expires=<the grant's absolute expiry>, never extending it.
    • On any failure it sets no cookie.
    • Either way it returns a 303 to the bare URL with no body. The grant is never reflected.
    • Headers: private, no-store, no-referrer, CSP default-src 'none'; frame-ancestors 'none'.
  4. The page is a server component. It reads the cookie, fetches 7- and 30-day stats server-to-server with the Bearer session, and renders:

    • conversations per UTC day, accepted messages, and delivered replies;
    • p50 and p95 latency;
    • daily unique visitors and visitor-days, with the brief's explanation;
    • clients, top countries with Other, and outcomes;
    • an "estimated (sampled)" note, plus empty, expired-link, and unavailable states.

    The session never reaches client components, storage, analytics, or logs. Page headers are private, no-store, no-referrer, CSP frame-ancestors 'none', and noindex.

  5. PostHog: dashboard paths are excluded from page views, and a before_send drops every event raised on a dashboard path.

Tests

  • 19 lib tests and 6 page tests cover:
    • cookie flags, path, and an absolute expiry equal to expiresAt;
    • 303 to the bare URL, with no cookie on failure and a 404 for an invalid handle;
    • headers, and the grant's absence from every response;
    • the Bearer session on stats fetches;
    • window allowlisting, the PostHog drop predicate, and the rendered states.
  • npm test: web 457/457, router 110/110. tsc is clean, npm run build succeeds, and verify:recommended-flows passes.
  • A local next start smoke test checked the grant 303 and its headers, the bare page's expired state, and the invalid-handle 404.

Not verified locally: the OpenNext cf:build Worker itself. The preview deploy will exercise the rewrite and headers.

🤖 Generated with Claude Code


Note

Medium Risk
Introduces grant-to-session auth, cookie scoping, and cross-origin exchange rules on a new sensitive surface; mitigated by server-only sessions, no grant echo, and broad test coverage.

Overview
Adds a private owner analytics dashboard at /u/<handle>/dashboard, opened via a single-use ?grant= link from the registry API.

Access flow: next.config.mjs rewrites grant-bearing requests to an exchange route before any React/PostHog runs. GET/HEAD shows a script-free confirmation page so scanners cannot burn the grant; only a same-origin POST redeems it server-to-server, sets a path-scoped HttpOnly session cookie, and 303s to the bare URL (failures redirect with ?link=used and never clear an existing cookie). The dashboard page is a server component that reads the cookie and fetches 7/30-day stats as Bearer auth only on the server.

Privacy: Dashboard paths get strict no-store/no-referrer/CSP/noindex headers; main content is marked for session-replay masking. PostHog adds dropAgentDashboardAnalytics in before_send and excludes dashboard routes from site page tracking.

UI: New dashboard page and CSS render comparisons, daily metrics, latency, visitor-days, and client/country/outcome breakdowns, with expired, empty, and unavailable states. agent-registry exports REGISTRY_ORIGIN and readBoundedBody for the new agent-dashboard helpers. Lib and page tests cover exchange, cookies, headers, stats validation, routing config, and analytics exclusion.

Reviewed by Cursor Bugbot for commit 895470b. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds the owner analytics dashboard at /u/<handle>/dashboard, reachable only through a 15-minute single-use grant link issued by POST /api/v1/agents/<handle>/manage/dashboard-link. Depends on the relay-agent exchange and stats endpoints in AgentWorkforce/relay-agent#16.

Flow and privacy

  • Grant-bearing requests are rewritten before filesystem routes to an exchange handler, so no page, layout, or PostHog code runs with the grant in the URL.
  • GET/HEAD on a grant link renders a static, script-free confirmation page; only its POST from an exact origin match (https scheme, host, and port) redeems the single-use grant, so link scanners and prefetchers can't burn it.
  • The handler trades the grant server-to-server, sets an HttpOnly, Secure, SameSite=Lax session cookie scoped to the dashboard path with the grant's absolute expiry, and 303s to the bare URL.
  • The grant is never reflected in any response; a failed or replayed exchange sets no cookie and redirects with ?link=used, so a Back press, double submit, or cross-site form can't log the owner out — the page keeps the current session and shows a notice.
  • The session is sent only as a Bearer token server-to-server and never reaches client components, storage, logs, or analytics; dashboard content is also masked from session replay.
  • Exchange and page responses are private, no-store, no-referrer, and CSP-locked; the page is noindex and dashboard paths are excluded from PostHog page views and captured events.
  • proxy.ts was not used because Next 16 proxies run on the Node runtime, which OpenNext Cloudflare rejects.

Dashboard content and test coverage

  • The server component renders 7- and 30-day comparisons plus per-day breakdowns of conversations, messages, replies, latency, visitor-days, clients, countries, and outcomes, with empty, expired, unavailable, and estimated states.
  • Adds 19 lib tests and 6 page tests; web (457) and router (110) suites pass, tsc and npm run build are clean, and a local next start smoke test verified the redirect, headers, expired state, and invalid-handle 404.

Written for commit 895470b. Summary will update on new commits.

View guided diff Turn on auto-fix


Agent Relay sessions

  • claude session 8fa30995 · opened via gh pr create · last active 2026-10-08

A dashboard link carries a 15-minute, single-use grant. next.config
rewrites the grant-bearing request (beforeFiles, keyed on the grant
query) to a route handler that exchanges it server-to-server with the
relay-agent registry, sets an HttpOnly/Secure/SameSite=Lax session
cookie scoped to the dashboard path with the session's absolute expiry,
and 303s to the bare URL. No page, layout, or PostHog code runs for the
exchange, and the grant is never echoed.

The force-dynamic page reads the cookie server-side, fetches 7- and
30-day stats with the Bearer session, and renders daily conversations,
messages, replies, latency, visitor-days, clients, countries, and
outcomes, with expired, unavailable, empty, and estimated states.
Exchange, redirect, and page responses are private/no-store,
no-referrer, and CSP-locked; the page is noindex. PostHog page views
and all captured events are dropped on dashboard paths.

A Node-runtime proxy.ts was not used: OpenNext Cloudflare 1.20 refuses
Node middleware at build time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Session-Id: 8fa30995-3394-4357-aadf-23aee1464578
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T07:11:19.283901Z 238a0da PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 02b31743-6637-4b8c-b557-15a24135887a
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Devin Review

Comment thread web/app/u/[handle]/dashboard/page.tsx Outdated
Comment thread web/lib/agent-dashboard.ts Outdated
Comment thread web/lib/site-analytics.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 238a0da0a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/next.config.mjs

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread web/lib/agent-dashboard.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread web/next.config.mjs
Comment thread web/app/u/[handle]/dashboard/exchange/route.ts
Comment thread web/app/u/[handle]/dashboard/page.tsx Outdated
Comment thread web/lib/agent-dashboard.ts Outdated
Comment thread web/app/u/[handle]/dashboard/dashboard.module.css Outdated
- GET/HEAD on a grant link renders a static, script-free confirmation; only
  its same-origin POST redeems the single-use grant, so link scanners,
  unfurlers and prefetchers cannot burn it.
- A failed exchange clears any older handle-scoped session cookie.
- The empty state considers every displayed metric.
- Dashboard content is blocked from session replay (ph-no-capture).
- Zero-conversation days render a zero-width bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Session-Id: 8fa30995-3394-4357-aadf-23aee1464578

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using high effort and found 1 potential issue.

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 ced0e22. Configure here.

Comment thread web/lib/agent-dashboard.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread web/lib/test/agent-dashboard.test.ts Outdated
Comment thread web/lib/agent-dashboard.ts Outdated
…n check

- A failed or replayed exchange redirects to ?link=used and leaves the
  cookie alone, so Back, a double submit, or a cross-site form cannot log the
  owner out; the page shows a notice above the current session.
- The exchange requires an exact https origin (scheme, host, and port).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Session-Id: 8fa30995-3394-4357-aadf-23aee1464578
@khaliqgant
khaliqgant merged commit 8d467d8 into main Oct 8, 2026
5 checks passed
@khaliqgant
khaliqgant deleted the feat/company-dashboard branch October 8, 2026 07:39
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Preview deployed!

Environment URL
Web https://f746323c-agentrelay-web.agent-workforce.workers.dev

This is a Cloudflare Workers preview version of this PR's build.

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.

1 participant