From b5cd4e0bb81730cce3a12bdb1e8c50374f04dc29 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 26 Jun 2026 09:24:46 +0100 Subject: [PATCH] fix(webapp): stop showing the in-dashboard agent to admins by default The in-dashboard agent panel was shown to all admins and impersonators regardless of the hasDashboardAgentAccess flag, so it appeared even where the agent is disabled. It is now gated by the flag for everyone. Admins and impersonators can opt into an everywhere-preview by setting DASHBOARD_AGENT_ADMIN_PREVIEW=1 (default off), separate from the per-org rollout flag. --- apps/webapp/app/env.server.ts | 5 ++++- apps/webapp/app/v3/canAccessDashboardAgent.server.ts | 10 +++++----- apps/webapp/app/v3/featureFlags.ts | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 2da52942477..b90e41040e2 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -108,8 +108,11 @@ const EnvironmentSchema = z DASHBOARD_AGENT_SECRET_KEY: z.string().optional(), // Global default for the `hasDashboardAgentAccess` flag. "0" (off) ships the // agent dark; flip to "1" to enable it for everyone at GA. Per-org overrides - // (org featureFlags) and admins/impersonators win regardless. + // (org featureFlags) win regardless. DASHBOARD_AGENT_ENABLED: z.string().default("0"), + // "1" gives admins/impersonators an everywhere-preview (default off), + // separate from the per-org rollout flag above. + DASHBOARD_AGENT_ADMIN_PREVIEW: z.string().default("0"), // Anthropic key for the dashboard agent's Head Start route only (the warm // first-turn step-1 LLM call runs in this process). The agent run itself // uses its own key on the Trigger side. When unset, Head Start is disabled diff --git a/apps/webapp/app/v3/canAccessDashboardAgent.server.ts b/apps/webapp/app/v3/canAccessDashboardAgent.server.ts index b453a694e71..dd3f4b0769a 100644 --- a/apps/webapp/app/v3/canAccessDashboardAgent.server.ts +++ b/apps/webapp/app/v3/canAccessDashboardAgent.server.ts @@ -5,10 +5,10 @@ import { makeFlag } from "~/v3/featureFlags.server"; /** * Whether the in-dashboard AI agent is available to this user in this org. - * Mirrors `canAccessAi`: admins/impersonators always pass, then the global / - * per-org feature flag with `DASHBOARD_AGENT_ENABLED` as the global default, so - * a per-org override (incl. disabling it) wins. Enforced server-side so a - * non-flagged user can't start sessions by hitting the resource route directly. + * Gated by the global / per-org `hasDashboardAgentAccess` flag, with + * `DASHBOARD_AGENT_ENABLED` as the global default (a per-org override wins). + * Admins/impersonators bypass it only when `DASHBOARD_AGENT_ADMIN_PREVIEW` is on + * (default off). Enforced server-side so a non-flagged user can't start sessions. */ export async function canAccessDashboardAgent(options: { userId: string; @@ -22,7 +22,7 @@ export async function canAccessDashboardAgent(options: { }): Promise { const { userId, isAdmin, isImpersonating, organizationSlug, orgFeatureFlags } = options; - if (isAdmin || isImpersonating) { + if ((isAdmin || isImpersonating) && env.DASHBOARD_AGENT_ADMIN_PREVIEW === "1") { return true; } diff --git a/apps/webapp/app/v3/featureFlags.ts b/apps/webapp/app/v3/featureFlags.ts index bcbdd09b258..46434bebf30 100644 --- a/apps/webapp/app/v3/featureFlags.ts +++ b/apps/webapp/app/v3/featureFlags.ts @@ -26,7 +26,7 @@ export const FeatureFlagCatalog = { [FEATURE_FLAG.hasLogsPageAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasAiAccess]: z.coerce.boolean(), // Gates the in-dashboard AI agent panel. Controllable globally and per-org - // (org wins); admins/impersonators always see it. Defaults off via DASHBOARD_AGENT_ENABLED. + // (org wins). Defaults off via DASHBOARD_AGENT_ENABLED. [FEATURE_FLAG.hasDashboardAgentAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasComputeAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasPrivateConnections]: z.coerce.boolean(),