[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

This commit is contained in:
ciregenz
2026-06-13 23:53:14 -07:00
parent 848b8eae97
commit b4f6a9e6e0
2 changed files with 41 additions and 24 deletions
@@ -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;
@@ -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' },
],