diff --git a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx index 30fd30b4..3f207ece 100644 --- a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx +++ b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx @@ -50,6 +50,16 @@ const OnboardingRoot: React.FC = () => { dispatch, ]); + // The welcome chat IS the user launching their first agent, so once it finishes, mark + // launch_agent done. Without this the post-win nudge repeats "Launch your first Agent" they + // just did: the skipIf baseline-capture re-arms on every completedSteps change and tends to + // snapshot the real send as "pre-existing", which freezes the step as un-completable. + useEffect(() => { + if (!progress.initialized || !firstAgentDone) return; + if ((progress.completedSteps ?? []).includes('launch_agent')) return; + dispatch(markStepCompleted('launch_agent')); + }, [firstAgentDone, progress.initialized, progress.completedSteps, dispatch]); + // First run: the cursor pops into existence, pauses, then moves to and clicks the New Agent // button (welcome_open step) which spawns the welcome chat. Fires once, only on the dashboard // with a way to run and nothing launched yet. Fail-safe: if the cursor can't run, a manual diff --git a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts index 902ac30d..7459ad38 100644 --- a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts +++ b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts @@ -59,7 +59,10 @@ export function isYoutubeEnabled(s: RootState): boolean { export function hasAnyAgentLaunched(s: RootState): boolean { const sessions = s.agents?.sessions ?? {}; - return Object.keys(sessions).length > 0; + // A draft is an unsent chat, not a launched agent. Counting drafts let the welcome draft + // pre-satisfy launch_agent at baseline-capture time, which froze the step as "pre-existing" + // so it never auto-completed, leaving "Launch your first Agent" stuck to-do after the chat. + return Object.values(sessions).some((x: any) => x?.status && x.status !== 'draft'); } /** True once any agent has actually FINISHED (not just started). Used to hold the