From b4f6a9e6e0019913db886a65714a53e004e763dd Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sat, 13 Jun 2026 23:53:14 -0700 Subject: [PATCH] [eric] onboarding: agentic cursor auto-opens the chat on first run and asks 'what do you want done?' (static, no LLM); user types their own thing --- .../components/Onboarding/OnboardingRoot.tsx | 32 +++++++++++++++++- .../Onboarding/steps/step03_launchAgent.ts | 33 ++++++------------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx index 89d9fd03..d21a049d 100644 --- a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx +++ b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx @@ -18,7 +18,12 @@ import { import AgenticCursor, { type AgenticCursorHandle } from './ac/AgenticCursor'; import { onboardingDirector } from './OnboardingDirector'; import { STEPS } from './steps'; -import { hasAnyAgentCompleted } from './steps/skipPredicates'; +import { + hasAnyAgentCompleted, + hasAnyAgentLaunched, + hasFreeTrialActive, + hasModelConnected, +} from './steps/skipPredicates'; import OnboardingPanel from './OnboardingPanel'; import { onboardingBus } from './eventBus'; import { report } from './telemetry'; @@ -33,6 +38,15 @@ const OnboardingRoot: React.FC = () => { const progress = useAppSelector((s) => s.onboardingProgress); const settingsLoaded = useAppSelector((s) => s.settings.loaded); const firstAgentDone = useAppSelector(hasAnyAgentCompleted); + const autoOpenedRef = useRef(false); + // Ready to auto-open the chat: a way to run exists, nothing launched yet, and the + // first step isn't already done. + const firstRunReady = useAppSelector( + (s) => + (hasFreeTrialActive(s) || hasModelConnected(s)) && + !hasAnyAgentLaunched(s) && + !s.onboardingProgress.completedSteps.includes('launch_agent'), + ); // The one gentle nudge: open the quiet pill ONCE, but only AFTER the first agent // actually finishes, so it never pops mid-run. Respects a panel they've hidden or @@ -50,6 +64,22 @@ const OnboardingRoot: React.FC = () => { dispatch, ]); + // First-run assist: the agentic cursor opens the chat and asks "what do you want + // done?" on its own, so the user never hunts for the input. Fires once, only on the + // dashboard with the AC idle. Fail-safe: if it can't run, the empty state + chat + // still work by hand, so a missed auto-open never blocks anything. + useEffect(() => { + if (autoOpenedRef.current || !progress.initialized || !firstRunReady) return; + if (onboardingDirector.isRunning()) return; + if (!window.location.hash.includes('/dashboard/')) return; + autoOpenedRef.current = true; + const t = window.setTimeout(() => { + if (onboardingDirector.isRunning()) return; + onboardingDirector.startStep('launch_agent', { x: window.innerWidth - 80, y: 110 }); + }, 1400); // let the dashboard + cursor settle before it drives + return () => window.clearTimeout(t); + }, [progress.initialized, firstRunReady]); + useEffect(() => { if (progress.initialized) return; if (!settingsLoaded) return; diff --git a/frontend/src/app/components/Onboarding/steps/step03_launchAgent.ts b/frontend/src/app/components/Onboarding/steps/step03_launchAgent.ts index e6c74ba0..02c259d3 100644 --- a/frontend/src/app/components/Onboarding/steps/step03_launchAgent.ts +++ b/frontend/src/app/components/Onboarding/steps/step03_launchAgent.ts @@ -1,12 +1,6 @@ import type { OnboardingStep } from './types'; import { S } from '../selectors'; -import { hasAnyAgentLaunched, isYoutubeEnabled, hasModelConnected, hasFreeTrialActive } from './skipPredicates'; - -// Primary: YouTube summary (needs MCP from step 2). Fallback uses built-in web tools (no MCP). -const YOUTUBE_PROMPT = - 'What is this youtube video about: https://youtu.be/_NKj8KQMY-k?si=rEk4KO2bOpa5Vo0z. Do not use browser agents.'; -const FALLBACK_PROMPT = - 'Find the latest news about AI from the web and give me a short summary.'; +import { hasAnyAgentLaunched, hasModelConnected, hasFreeTrialActive } from './skipPredicates'; export const step03: OnboardingStep = { id: 'launch_agent', @@ -16,31 +10,24 @@ export const step03: OnboardingStep = { // instead, which restores today's flow exactly (no trial = no regression). index: 1, title: 'Launch your first Agent', - description: 'Click the chat bubble to fire up a new Agent in a dashboard.', + description: 'Tell the chat what you want done and a team gets to work.', videoSrc: './onboarding-videos/v2/03.mp4', videoDurationLabel: '0:24', skipIf: (s) => hasAnyAgentLaunched(s) || (!hasModelConnected(s) && !hasFreeTrialActive(s)), requiresDashboard: true, + // The cursor opens the chat FOR the user, then asks what they want. No canned + // prompt and no LLM here: it's a static move + simulated click + a hardcoded + // line; the user types their own thing and their team runs. ops: [ { kind: 'move_to', target: S.newAgentButton }, - { kind: 'popup', text: 'Tap the chat bubble to start a fresh chat.' }, - { - kind: 'wait_user', - condition: { kind: 'click_target', target: S.newAgentButton }, - }, - { - kind: 'type_into', - target: S.chatInput, - // YouTube prompt bans browser agents (MCP handles it); fallback uses web tools by design. - text: (state) => (isYoutubeEnabled(state) ? YOUTUBE_PROMPT : FALLBACK_PROMPT), - speedMs: 12, - }, - { kind: 'move_to', target: S.chatSendButton }, - { kind: 'click', target: S.chatSendButton, simulate: true }, + { kind: 'popup', text: 'Let me open a chat for you.' }, + { kind: 'click', target: S.newAgentButton, simulate: true }, + { kind: 'move_to', target: S.chatInput }, + { kind: 'popup', text: "What do you want done? Type it here and I'll put a team on it." }, { kind: 'wait_user', condition: { kind: 'event_bus', event: 'chat:message_sent' }, - timeoutMs: 30000, + timeoutMs: 180000, }, { kind: 'outro' }, ],