import type { IntegrationSetupState, Me } from "@rakazo/contracts"; import { collectLocalProfileSignals, type UserProfileSignals } from "./user-profile-harvest"; /** Sentinel stored in discovery `detail`; the shell renders it with Lingui. */ export const DISCOVERY_MAIN_BOT_ROLE = "main-bot-role"; export type DiscoverySourceId = "profile-basics" | "connected-tools" | "specialist-crew"; export type DiscoveryStatus = "pending" | "scanning" | "found" | "skipped"; export interface DiscoveryItem { id: DiscoverySourceId; status: DiscoveryStatus; detail?: string; } export type OnboardingDiscoveryPhase = "loading" | "emoji" | "integrations" | "assembly"; export interface DiscoveryContext { phase: OnboardingDiscoveryPhase; me?: Pick< Me, "name" | "email" | "needsModel" | "defaultProvider" | "defaultModel" | "computerHost" > | null; integrationSetup?: IntegrationSetupState | null; connectedIntegrationCount?: number; profileSignals?: UserProfileSignals; profilePersisted?: boolean; crewReady?: boolean; } export function summarizeProfileSignals(signals: UserProfileSignals): string { const parts: string[] = []; if (signals.name) parts.push(signals.name); parts.push(signals.timezone); if (signals.uiLocale && signals.uiLocale !== "en") parts.push(signals.uiLocale); return parts.join(" ยท "); } export function countConfiguredIntegrations( setup: IntegrationSetupState | null | undefined, ): number { return setup?.providers.filter((provider) => provider.configured).length ?? 0; } export function computeDiscoveryItems(context: DiscoveryContext): DiscoveryItem[] { const signals = context.profileSignals ?? (context.me ? collectLocalProfileSignals(context.me) : null); const configuredCount = context.connectedIntegrationCount ?? countConfiguredIntegrations(context.integrationSetup); const profile: DiscoveryItem = (() => { if (context.profilePersisted && signals) { return { id: "profile-basics", status: "found", detail: summarizeProfileSignals(signals) }; } if (context.phase === "assembly" && signals) { return { id: "profile-basics", status: "scanning", detail: summarizeProfileSignals(signals) }; } if (signals && context.me) { return { id: "profile-basics", status: "found", detail: summarizeProfileSignals(signals) }; } if (context.phase === "loading") { return { id: "profile-basics", status: "scanning" }; } return { id: "profile-basics", status: "pending" }; })(); const tools: DiscoveryItem = (() => { if (configuredCount > 0) { return { id: "connected-tools", status: "found", detail: String(configuredCount), }; } if (context.phase === "integrations") { return { id: "connected-tools", status: "scanning" }; } if (context.integrationSetup && !context.integrationSetup.needsSetup) { return { id: "connected-tools", status: "skipped" }; } return { id: "connected-tools", status: "pending" }; })(); const crew: DiscoveryItem = (() => { if (context.crewReady) { return { id: "specialist-crew", status: "found", detail: DISCOVERY_MAIN_BOT_ROLE }; } if (context.phase === "assembly") { return { id: "specialist-crew", status: "scanning" }; } return { id: "specialist-crew", status: "pending" }; })(); return [profile, tools, crew]; } export function visibleDiscoveryItems(items: DiscoveryItem[]): DiscoveryItem[] { return items; } export function discoveryProgress(items: DiscoveryItem[]): { done: number; total: number; active: DiscoveryItem | null; } { const done = items.filter((item) => item.status === "found" || item.status === "skipped").length; const active = items.find((item) => item.status === "scanning") ?? items.find((item) => item.status === "pending") ?? null; return { done, total: items.length, active }; }