* Production readiness phase 1: journeys doc, onboarding persistence, teardown, chat UX - Add docs/PRODUCTION-READINESS.md with journey matrix and defect backlog - Persist onboarding draft in session storage across reload - Localize onboarding discovery main-bot role; German Chief of Staff string - Destroy team sandboxes and home folders when deleting an account - Keep composer/transcript visible during queued runs; extend send watchdog - Add Playwright specs: reload persistence, DE intake, queued progress, console guard Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix CI: sync Lingui catalogs, onboarding name draft, E2E stability - Localize Chief of Staff in discovery shell; sync all locale PO files - Propagate Oo name to onboarding draft while typing - Adjust DE intake, queued-run, and console-error E2E specs Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Format chat-queued-progress E2E spec Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(api): scope account-delete sandbox teardown to owned spaces Only destroy team sandboxes and homes for spaces the deleting user owns with no remaining members or bots. Guard home removal under dataDir/homes. Add unit and postgres journey coverage. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * chore: apply biome formatting Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
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 };
|
|
}
|