Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions apps/docs/self-hosting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,23 @@ After you upgrade, Roomote can surface the new release in the web app:
wizard creates the GitHub App; other providers use their documented OAuth or
credential flow.
- For Slack, Microsoft Teams, Telegram, and Discord: a workspace or server where
you can install an app or bot; the setup wizard prefills the Slack app
you can install an app or bot; the setup assistant prefills the Slack app
manifest and creates Discord's installation link for you.

## Verify the deployment

The final setup step offers a small set of preselected starter tasks. Keep the
ones you want, clear any you do not want to run yet, then select **Go**. If you
start one task, Roomote opens it directly; if you start several, Roomote opens
the task list. You can also clear every selection and finish setup without
starting a task.
Setup ends in a conversational session with Roomote. Once sign-in, inference,
and a usable sandbox provider are confirmed, the setup wizard is replaced by a
persistent chat session: Roomote maintains an onboarding agenda in the
session, and you connect source control through the trusted side panel (the
same panel opens as a sheet on smaller screens). Credential entry and OAuth
always happen in that panel, never in chat.

When repositories are connected, Roomote offers the preselected starter tasks
in one structured multi-select question; choose at least one and it launches
them as visible tasks attached to the same session. The first successful
launch completes setup, and the session continues as a normal session.
Automation recommendations appear inline afterwards and are optional.

After setup, run a small task that uses the first environment if you did not
start one from the starter list. A healthy deployment should let you:
Expand Down
137 changes: 137 additions & 0 deletions apps/web/src/app/(onboarding)/setup/SetupConversationalSetup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use client';

import { useEffect } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';

import { useUser } from '@/hooks/useUser';
import { useTRPC } from '@/trpc/client';
import type { FastSessionMessage } from '@/lib/server/fast-sessions';

import { FastSessionTranscript } from '../../(sandbox)/sessions/[sessionId]/FastSessionTranscript';
import { SetupRecommendationsInlineCard } from './SetupRecommendationsInlineCard';
import {
SetupSourceControlPanelSurface,
useSetupRouteTransition,
useSetupSourceControlMilestoneEffect,
useSetupSourceControlStatus,
} from './SetupSourceControlPanel';

/**
* Conversational setup workspace: the persisted Fast transcript is the
* primary surface, with the trusted source-control side panel (sheet on
* smaller screens) and inline automation recommendations around it.
*/
export function SetupConversationalSetup() {
const trpc = useTRPC();
const { isSignedIn, user } = useUser();
const isAdmin = user?.isAdmin === true;
const enabled = isSignedIn && isAdmin;

const statusQuery = useQuery(
trpc.setup.sessionStatus.queryOptions(undefined, { enabled }),
);
const createSession = useMutation(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutation result is discarded and sessionStatus is neither invalidated nor refetched. If its initial request observes no setup session (including the normal race where it completes before this mutation), sessionId remains null and the page stays on "Preparing your setup session..." until a reload. Use the returned session ID or invalidate/refetch setup.sessionStatus after success; also avoid reissuing creation while the mutation is pending.

trpc.setup.getOrCreateSession.mutationOptions(),
);

useEffect(() => {
if (enabled && statusQuery.data?.sessionId == null) {
createSession.mutate();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enabled, statusQuery.data?.sessionId]);

const sessionId = statusQuery.data?.sessionId ?? null;
const setupCompleted = statusQuery.data?.completed ?? false;

useSetupRouteTransition({ sessionId, completed: setupCompleted });

const { sourceControlSetup, connectedProviderCount } =
useSetupSourceControlStatus(enabled);
useSetupSourceControlMilestoneEffect({
enabled: enabled && Boolean(sessionId),
connectedProviderCount,
});
const messagesQuery = useQuery(
trpc.fastSessions.messages.queryOptions(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sessionStatus returns setupSession.sessionId, but getOrCreateSetupSessionCommand stores that as the unified sessions.id. The fastSessions.* APIs called here look up fastAgentConversations.id, so the initial transcript request, replies, and structured-input submission all fail with "Fast session not found". Expose/use the linked fast-conversation ID for this surface and retain the unified ID only for /sessions/{id} routing.

{ sessionId: sessionId! },
{
enabled: Boolean(sessionId),
// The SSE stream in the transcript keeps rows fresh; this query only
// seeds the initial transcript snapshot.
staleTime: Infinity,
refetchOnWindowFocus: false,
},
),
);
const sessionTasksQuery = useQuery(
trpc.fastSessions.tasks.queryOptions(
{ sessionId: sessionId! },
{ enabled: Boolean(sessionId) },
),
);

const sourceControlConnected = connectedProviderCount > 0;

if (!enabled) {
return null;
}

const sessionIdValue = sessionId;
const initialMessages = (messagesQuery.data?.messages ??
[]) as unknown as FastSessionMessage[];
const hasOlderMessages = messagesQuery.data?.hasOlderMessages ?? false;
const timelineExtras = (
<div className="space-y-4">
{sessionTasksQuery.data && sessionTasksQuery.data.length > 0 ? (
<div className="rounded-2xl border border-border bg-card p-4">
<p className="mb-3 text-sm font-medium">Launched tasks</p>
<ul className="space-y-2">
{sessionTasksQuery.data.map((task) => (
<li key={task.taskId} className="text-sm">
<a
className="underline decoration-border underline-offset-2 hover:decoration-foreground"
href={`/task/${task.taskId}`}
>
{task.title || task.taskId}
</a>
</li>
))}
</ul>
</div>
) : null}
{sourceControlConnected ? <SetupRecommendationsInlineCard /> : null}
</div>
);

return (
// Break out of the centered setup column: the conversational workspace is
// a full-height two-pane surface, not a narrow wizard step.
<div className="relative left-[calc(-50vw+50%)] flex h-[calc(var(--effective-viewport-height)-8rem)] w-screen flex-col gap-4 px-2 lg:flex-row">
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-border bg-background">
{sessionIdValue ? (
<FastSessionTranscript
sessionId={sessionIdValue}
initialMessages={initialMessages}
hasOlderMessages={hasOlderMessages}
canReply
initialTitle="Set up Roomote."
fallbackTitle="Set up Roomote."
timelineExtras={timelineExtras}
/>
) : (
<div className="p-6 text-sm text-muted-foreground">
Preparing your setup session…
</div>
)}
</div>
{sourceControlSetup && !setupCompleted ? (
<div className="shrink-0 overflow-y-auto lg:w-[24rem]">
<SetupSourceControlPanelSurface
sourceControlSetup={sourceControlSetup}
/>
</div>
) : null}
</div>
);
}
11 changes: 2 additions & 9 deletions apps/web/src/app/(onboarding)/setup/SetupDocs.client.test.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import { useMutation } from '@tanstack/react-query';

import { useUser } from '@/hooks/useUser';
import { useTRPC } from '@/trpc/client';

import { StepAutomationRecommendations } from './StepAutomationRecommendations';

/**
* Inline automation-recommendations card. Rendered in the conversational
* setup workspace and in the setup session's normal route after activation.
* Apply or Skip notifies Roomote so it can acknowledge the choice and
* continue naturally. Optional: never blocks activation or launched tasks.
*/
export function SetupRecommendationsInlineCard() {
const trpc = useTRPC();
const { user } = useUser();
const notifyRecommendationChoice = useMutation(
trpc.setup.sessionMilestone.mutationOptions(),
);

if (user?.isAdmin !== true) {
return null;
}

return (
<div className="rounded-2xl border border-border bg-card p-4">
<p className="mb-3 text-sm font-medium">Recommended automations</p>
<StepAutomationRecommendations
onContinue={() => {
notifyRecommendationChoice.mutate({
milestone: 'recommendations_notified',
eventType: 'recommendations_decided',
});
}}
/>
</div>
);
}
Loading
Loading