import React, { createContext, useContext, useMemo, type ReactNode } from 'react'; import { canCreateManagedTelegramBot, canManageSuperagent } from './agentPermissions'; import { tierAllowsBestSuperagentModels } from '../features/settings/modelOptions'; import type { SuperagentAgent, SuperagentUser } from '../types'; /** * Holds the signed-in platform user (the `/me` payload) injected by the host via * the `user` prop on `SuperagentHomeScreen`, and derives feature-flag and * capability reads off it. * * The package never fetches the user — auth is a shell concern (see CLAUDE.md). * It only consumes what the host provides. The hooks mirror the web builder's * `useFeatureFlags` / `useCapability`, minus the web-only PostHog exposure * tracking (also a shell concern). The native package can't import from * `apps/builder`, so the `Capability` enum and role check are mirrored here. */ /** * Capability keys matching the backend Capability enum (the keys returned in the * `/me` `allowed_capabilities`). Mirror of the web builder's `Capability` enum. */ export enum Capability { // Development features BACKEND_FUNCTIONS = 'backend_functions', CONNECTORS = 'connectors', INTEGRATIONS = 'integrations', CUSTOM_INTEGRATIONS = 'custom_integrations', MCP_CONNECTIONS = 'mcp_connections', WORKSPACE_SKILLS = 'workspace_skills', CUSTOM_DOMAINS = 'custom_domains', CODE_EDITING = 'code_editing', STATIC_CODE_ANALYSIS = 'static_code_analysis', GITHUB_INTEGRATION = 'github_integration', DEV_STAGING = 'dev_staging', MANUAL_MODEL_SELECTION = 'manual_model_selection', CUSTOM_GOOGLE_OAUTH = 'custom_google_oauth', MOBILE_BUILDS = 'mobile_builds', CODE_EXPORT = 'code_export', SCREEN_RECORDINGS = 'screen_recordings', // Apps PRIVATE_APPS = 'private_apps', INTERNAL_VISIBILITY = 'internal_visibility', UNLIMITED_APPS = 'unlimited_apps', // Branding REMOVE_BADGE = 'remove_badge', // Security SSO = 'sso', ENFORCE_WORKSPACE_SSO = 'enforce_workspace_sso', SCIM = 'scim', APP_SSO = 'app_sso', IP_WHITELISTING = 'ip_whitelisting', OPT_OUT_DATA_TRAINING = 'opt_out_data_training', AUDIT_LOGS = 'audit_logs', WORKSPACE_API_KEYS = 'workspace_api_keys', // Team ADVANCED_ROLE_PERMISSIONS = 'advanced_role_permissions', RESTRICT_MEMBER_APP_VISIBILITY = 'restrict_member_app_visibility', PUBLISH_GOVERNANCE = 'publish_governance', MEMBER_CREDIT_LIMITS = 'member_credit_limits', // Support WORKSPACE_MONITORING = 'workspace_monitoring', PREMIUM_SUPPORT = 'premium_support', PRIVATE_TEMPLATES = 'private_templates', // AI Agents AGENT_WEBHOOKS = 'agent_webhooks', // Marketplace PAID_LISTINGS = 'paid_listings', // Data Residency DATA_RESIDENCY = 'data_residency', // Connector Governance CONNECTOR_GOVERNANCE = 'connector_governance', // Enterprise operational controls & defaults SUPERAGENT_CONTROLS = 'superagent_controls', APP_TRANSFER_GOVERNANCE = 'app_transfer_governance', RATE_LIMIT_MULTIPLIER = 'rate_limit_multiplier', EXCLUDE_COST_SAVING_MODELS = 'exclude_cost_saving_models', DEDICATED_EMAIL_IP_POOL = 'dedicated_email_ip_pool', ADVANCED_SECURITY_POSTURE = 'advanced_security_posture', SECURE_WORKSPACE_DEFAULTS = 'secure_workspace_defaults', // CRM Metadata HUBSPOT_ID = 'hubspot_id', } // Mirror of the builder's `isPlatformAdminOrSupportPersonnel`: platform admins, // owners, and support personnel bypass capability gates so they can investigate // customer apps without being blocked by upgrade overlays. const isPlatformAdminOrSupportPersonnel = (platformRole?: string | null): boolean => platformRole === 'platform_admin' || platformRole === 'platform_owner' || platformRole === 'support_personnel'; const UserContext = createContext(null); export function UserProvider({ user, children, }: { user?: SuperagentUser | null; children: ReactNode; }) { // Memoize on the user reference so consumers only re-render when the host swaps // the user object (e.g. after a /me refresh), not on every parent render. const value = useMemo(() => user ?? null, [user]); return {children}; } /** * Feature-flag reads off the injected user. `hasFlag(name)` returns whether a * flag is enabled; `getFlagVariant(name)` returns the active variant key (or null). */ export function useFeatureFlags(): { hasFlag: (flagName: string) => boolean; getFlagVariant: (flagName: string) => string | null; } { const user = useContext(UserContext); const flags = user?.feature_flags; const variants = user?.feature_flag_variants; return useMemo( () => ({ hasFlag: (flagName: string) => flags?.includes(flagName) ?? false, getFlagVariant: (flagName: string) => variants?.[flagName] ?? null, }), [flags, variants], ); } /** * Check if the user has a specific capability. Reads from `allowed_capabilities` * (the single source of truth from `/me`); platform admins and support personnel * bypass the gate. Same logic as the web builder's `useCapability`, with the user * sourced from the injected context instead of an auth hook. */ export function useCapability(capability: Capability): boolean { const user = useContext(UserContext); return useMemo(() => { if (isPlatformAdminOrSupportPersonnel(user?.platform_role)) return true; if (!user?.allowed_capabilities) return false; return user.allowed_capabilities.includes(capability); }, [user?.allowed_capabilities, user?.platform_role, capability]); } export function useCanManageSuperagent( agent: Pick, ): boolean { const user = useContext(UserContext); return canManageSuperagent(agent, user); } export function useCanCreateManagedTelegramBot( agent: Pick, ): boolean { const user = useContext(UserContext); return canCreateManagedTelegramBot(agent, user); } /** * Whether the user's tier can select the "best" Superagent models (tier ≥ Builder). * Derived from the injected `/me` user, mirroring the web `useSuperagentModelAccess` * (prefer `subscription_pricing_tier`, fall back to `subscription_tier`). */ export function useSuperagentModelAccess(): { canSelectBestModel: boolean; effectiveTier: string | null; } { const user = useContext(UserContext); const effectiveTier = user?.subscription_pricing_tier || user?.subscription_tier || null; return useMemo( () => ({ canSelectBestModel: tierAllowsBestSuperagentModels(effectiveTier), effectiveTier, }), [effectiveTier], ); }