From 5fd158243d39046cea71d23c60313b3997a76bf0 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 15 Jul 2026 14:43:30 -0700 Subject: [PATCH] [eric] onboarding: opt-in usage read (chatgpt memory + claude titles from logged-in card -> prep profile, raw discarded) --- backend/apps/onboarding/models.py | 1 + backend/apps/onboarding/prep.py | 5 +- .../OnboardingV3/OnboardingV3Root.tsx | 10 +++- .../OnboardingV3/beats/BeatConnect.tsx | 19 ++++++- .../OnboardingV3/onboardingV3Api.ts | 4 +- .../OnboardingV3/useOnboardingV3Pipeline.ts | 27 +++++++++- frontend/src/shared/providerUsage.ts | 50 +++++++++++++++++++ 7 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 frontend/src/shared/providerUsage.ts diff --git a/backend/apps/onboarding/models.py b/backend/apps/onboarding/models.py index 5470a561..41cc0b79 100644 --- a/backend/apps/onboarding/models.py +++ b/backend/apps/onboarding/models.py @@ -46,6 +46,7 @@ class PrepRequest(BaseModel): scan: Optional[ScanResult] = None picked_apps: List[str] = Field(default_factory=list) identity: List[ProviderIdentity] = Field(default_factory=list) + usage_summary: str = "" class PrepResponse(BaseModel): diff --git a/backend/apps/onboarding/prep.py b/backend/apps/onboarding/prep.py index b2329e54..ab507880 100644 --- a/backend/apps/onboarding/prep.py +++ b/backend/apps/onboarding/prep.py @@ -26,8 +26,9 @@ P_SYSTEM = ( "organize local files, browse the web in a real browser, build small apps, and run agents in parallel. " "Given facts about the user's machine and the apps they picked, respond with STRICT JSON only: " '{"greeting": string, "starters": [{"title": string, "prompt": string}], "app_title": string, "app_prompt": string}. ' - "First, silently infer a short profile of this user from the facts (developer or designer or student, work or " - "personal, from apps, folders, plan tier, and email domain); tune every task to that profile and do not output it. " + "First, silently infer a short profile of this user. If usage_summary is present it is the STRONGEST signal (it is " + "what they actually ask their AI about and facts their AI remembers about them); weight it above everything else, " + "then apps, folders, plan tier, email domain. Tune every task and the personal app to that profile; do not output the profile. " "Exactly 4 starters. Each title is 2-5 words. Each prompt is a concrete, safe, immediately runnable task " "referencing the user's real folders, file counts, or picked apps when possible; never invent facts, never " "propose deleting anything without review. The FIRST starter must be an audit sized for parallel sub-work " diff --git a/frontend/src/app/components/OnboardingV3/OnboardingV3Root.tsx b/frontend/src/app/components/OnboardingV3/OnboardingV3Root.tsx index 476cf582..ea59cb7b 100644 --- a/frontend/src/app/components/OnboardingV3/OnboardingV3Root.tsx +++ b/frontend/src/app/components/OnboardingV3/OnboardingV3Root.tsx @@ -4,6 +4,7 @@ import { ArrowRight } from 'lucide-react'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { updateSettingsPatch } from '@/shared/state/settingsSlice'; import { setFlowActive } from '@/shared/state/onboardingV3Slice'; +import { selectSubscriptionConnections } from '@/shared/state/subscriptionsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import type { ClaudeTokens } from '@/shared/styles/claudeTokens'; import { useOnboardingV3Pipeline } from './useOnboardingV3Pipeline'; @@ -115,15 +116,18 @@ const OnboardingV3Root: React.FC = () => { const pipeline = useOnboardingV3Pipeline(); const [beat, setBeat] = useState('welcome'); const [scanConsent, setScanConsent] = useState(true); + const [usageConsent, setUsageConsent] = useState(true); const [picks, setPicks] = useState([]); + const connectedProvider = useAppSelector((s) => selectSubscriptionConnections(s).find((cx) => cx.isActive !== false)?.provider ?? null); const [finishing, setFinishing] = useState(false); - const { kickIdentity, kickScan, kickPrep, finish } = pipeline; + const { kickIdentity, kickScan, kickUsageRead, kickPrep, finish } = pipeline; const onConnected = useCallback(() => { kickIdentity(); kickScan(scanConsent); - }, [kickIdentity, kickScan, scanConsent]); + if (connectedProvider) kickUsageRead(connectedProvider, usageConsent); + }, [kickIdentity, kickScan, kickUsageRead, scanConsent, usageConsent, connectedProvider]); const leaveConnect = useCallback(() => { kickScan(scanConsent); @@ -190,6 +194,8 @@ const OnboardingV3Root: React.FC = () => { identity={pipeline.identity} scanConsent={scanConsent} setScanConsent={setScanConsent} + usageConsent={usageConsent} + setUsageConsent={setUsageConsent} onConnected={onConnected} onNext={leaveConnect} onBack={() => setBeat('newos')} diff --git a/frontend/src/app/components/OnboardingV3/beats/BeatConnect.tsx b/frontend/src/app/components/OnboardingV3/beats/BeatConnect.tsx index 8027a924..03259f5e 100644 --- a/frontend/src/app/components/OnboardingV3/beats/BeatConnect.tsx +++ b/frontend/src/app/components/OnboardingV3/beats/BeatConnect.tsx @@ -19,10 +19,12 @@ const BeatConnect: React.FC<{ identity: ProviderIdentity[]; scanConsent: boolean; setScanConsent: (v: boolean) => void; + usageConsent: boolean; + setUsageConsent: (v: boolean) => void; onConnected: () => void; onNext: () => void; onBack: () => void; -}> = ({ c, identity, scanConsent, setScanConsent, onConnected, onNext, onBack }) => { +}> = ({ c, identity, scanConsent, setScanConsent, usageConsent, setUsageConsent, onConnected, onNext, onBack }) => { const dispatch = useAppDispatch(); const connected = useAppSelector((s) => hasModelConnected(s)); const freeTrial = useAppSelector((s) => hasFreeTrialActive(s)); @@ -149,6 +151,21 @@ const BeatConnect: React.FC<{ Take a quick local look around to personalize my setup. Nothing leaves this Mac. +